mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Move IStorage classes to the new StorageBase
This commit is contained in:
parent
bfc343e801
commit
162fb4e389
25 changed files with 324 additions and 195 deletions
|
@ -21,7 +21,7 @@ namespace LibHac.Fs
|
|||
FsClient = Handle.File.Parent.FsClient;
|
||||
}
|
||||
|
||||
public override Result ReadImpl(long offset, Span<byte> destination)
|
||||
protected override Result ReadImpl(long offset, Span<byte> destination)
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
|
@ -36,7 +36,7 @@ namespace LibHac.Fs
|
|||
}
|
||||
}
|
||||
|
||||
public override Result WriteImpl(long offset, ReadOnlySpan<byte> source)
|
||||
protected override Result WriteImpl(long offset, ReadOnlySpan<byte> source)
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
|
@ -51,19 +51,19 @@ namespace LibHac.Fs
|
|||
}
|
||||
}
|
||||
|
||||
public override Result FlushImpl()
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
return FsClient.FlushFile(Handle);
|
||||
}
|
||||
|
||||
public override Result SetSizeImpl(long size)
|
||||
protected override Result SetSizeImpl(long size)
|
||||
{
|
||||
FileSize = InvalidSize;
|
||||
|
||||
return FsClient.SetFileSize(Handle, size);
|
||||
}
|
||||
|
||||
public override Result GetSizeImpl(out long size)
|
||||
protected override Result GetSizeImpl(out long size)
|
||||
{
|
||||
size = default;
|
||||
|
||||
|
|
|
@ -105,10 +105,10 @@
|
|||
|
||||
public static Result UnsupportedOperation => new Result(ModuleFs, 6300);
|
||||
public static Result SubStorageNotResizable => new Result(ModuleFs, 6302);
|
||||
public static Result SubStorageNotResizableMiddleOfFile => new Result(ModuleFs, 6302);
|
||||
public static Result UnsupportedOperationInMemoryStorageSetSize => new Result(ModuleFs, 6316);
|
||||
public static Result UnsupportedOperationInHierarchicalIvfcStorageSetSize => new Result(ModuleFs, 6304);
|
||||
public static Result SubStorageNotResizableMiddleOfFile => new Result(ModuleFs, 6303);
|
||||
public static Result UnsupportedOperationInMemoryStorageSetSize => new Result(ModuleFs, 6304);
|
||||
public static Result UnsupportedOperationInAesCtrExStorageWrite => new Result(ModuleFs, 6310);
|
||||
public static Result UnsupportedOperationInHierarchicalIvfcStorageSetSize => new Result(ModuleFs, 6316);
|
||||
public static Result UnsupportedOperationInIndirectStorageWrite => new Result(ModuleFs, 6324);
|
||||
public static Result UnsupportedOperationInIndirectStorageSetSize => new Result(ModuleFs, 6325);
|
||||
public static Result UnsupportedOperationInRoGameCardStorageWrite => new Result(ModuleFs, 6350);
|
||||
|
|
|
@ -9,11 +9,11 @@ namespace LibHac.Fs
|
|||
private int _disposedState;
|
||||
private bool IsDisposed => _disposedState != 0;
|
||||
|
||||
public abstract Result ReadImpl(long offset, Span<byte> destination);
|
||||
public abstract Result WriteImpl(long offset, ReadOnlySpan<byte> source);
|
||||
public abstract Result FlushImpl();
|
||||
public abstract Result GetSizeImpl(out long size);
|
||||
public abstract Result SetSizeImpl(long size);
|
||||
protected abstract Result ReadImpl(long offset, Span<byte> destination);
|
||||
protected abstract Result WriteImpl(long offset, ReadOnlySpan<byte> source);
|
||||
protected abstract Result FlushImpl();
|
||||
protected abstract Result GetSizeImpl(out long size);
|
||||
protected abstract Result SetSizeImpl(long size);
|
||||
|
||||
public Result Read(long offset, Span<byte> destination)
|
||||
{
|
||||
|
|
|
@ -23,7 +23,7 @@ namespace LibHac.Fs
|
|||
Size = size;
|
||||
}
|
||||
|
||||
public override Result ReadImpl(long offset, Span<byte> destination)
|
||||
protected override Result ReadImpl(long offset, Span<byte> destination)
|
||||
{
|
||||
if (BaseStorage == null) return ResultFs.SubStorageNotInitialized.Log();
|
||||
if (destination.Length == 0) return Result.Success;
|
||||
|
@ -33,7 +33,7 @@ namespace LibHac.Fs
|
|||
return BaseStorage.Read(Offset + offset, destination);
|
||||
}
|
||||
|
||||
public override Result WriteImpl(long offset, ReadOnlySpan<byte> source)
|
||||
protected override Result WriteImpl(long offset, ReadOnlySpan<byte> source)
|
||||
{
|
||||
if (BaseStorage == null) return ResultFs.SubStorageNotInitialized.Log();
|
||||
if (source.Length == 0) return Result.Success;
|
||||
|
@ -43,14 +43,14 @@ namespace LibHac.Fs
|
|||
return BaseStorage.Write(Offset + offset, source);
|
||||
}
|
||||
|
||||
public override Result FlushImpl()
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
if (BaseStorage == null) return ResultFs.SubStorageNotInitialized.Log();
|
||||
|
||||
return BaseStorage.Flush();
|
||||
}
|
||||
|
||||
public override Result SetSizeImpl(long size)
|
||||
protected override Result SetSizeImpl(long size)
|
||||
{
|
||||
if (BaseStorage == null) return ResultFs.SubStorageNotInitialized.Log();
|
||||
if (!IsResizable) return ResultFs.SubStorageNotResizable.Log();
|
||||
|
@ -72,7 +72,7 @@ namespace LibHac.Fs
|
|||
return Result.Success;
|
||||
}
|
||||
|
||||
public override Result GetSizeImpl(out long size)
|
||||
protected override Result GetSizeImpl(out long size)
|
||||
{
|
||||
size = default;
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ namespace LibHac.FsService.Creators
|
|||
imageHash.CopyTo(ImageHash);
|
||||
}
|
||||
|
||||
public override Result ReadImpl(long offset, Span<byte> destination)
|
||||
protected override Result ReadImpl(long offset, Span<byte> destination)
|
||||
{
|
||||
// In secure mode, if Handle is old and the card's device ID and
|
||||
// header hash are still the same, Handle is updated to the new handle
|
||||
|
@ -95,22 +95,22 @@ namespace LibHac.FsService.Creators
|
|||
return GameCard.Read(Handle, offset, destination);
|
||||
}
|
||||
|
||||
public override Result WriteImpl(long offset, ReadOnlySpan<byte> source)
|
||||
protected override Result WriteImpl(long offset, ReadOnlySpan<byte> source)
|
||||
{
|
||||
return ResultFs.UnsupportedOperationInRoGameCardStorageWrite.Log();
|
||||
}
|
||||
|
||||
public override Result FlushImpl()
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public override Result SetSizeImpl(long size)
|
||||
protected override Result SetSizeImpl(long size)
|
||||
{
|
||||
return ResultFs.UnsupportedOperationInRoGameCardStorageSetSize.Log();
|
||||
}
|
||||
|
||||
public override Result GetSizeImpl(out long size)
|
||||
protected override Result GetSizeImpl(out long size)
|
||||
{
|
||||
size = 0;
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ namespace LibHac.FsSystem
|
|||
return ResultFs.UnsupportedOperationInAesCtrExStorageWrite.Log();
|
||||
}
|
||||
|
||||
public override Result Flush()
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
return Result.Success;
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ namespace LibHac.FsSystem
|
|||
return base.WriteImpl(offset, _tempBuffer.AsSpan(0, size));
|
||||
}
|
||||
|
||||
public override Result Flush()
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
return BaseStorage.Flush();
|
||||
}
|
||||
|
|
|
@ -8,7 +8,8 @@ namespace LibHac.FsSystem
|
|||
{
|
||||
private IStorage BaseStorage { get; }
|
||||
private int BlockSize { get; }
|
||||
private long _length;
|
||||
private long Length { get; set; }
|
||||
private bool LeaveOpen { get; }
|
||||
|
||||
private LinkedList<CacheBlock> Blocks { get; } = new LinkedList<CacheBlock>();
|
||||
private Dictionary<long, LinkedListNode<CacheBlock>> BlockDict { get; } = new Dictionary<long, LinkedListNode<CacheBlock>>();
|
||||
|
@ -17,9 +18,10 @@ namespace LibHac.FsSystem
|
|||
{
|
||||
BaseStorage = baseStorage;
|
||||
BlockSize = blockSize;
|
||||
BaseStorage.GetSize(out _length).ThrowIfFailure();
|
||||
LeaveOpen = leaveOpen;
|
||||
|
||||
if (!leaveOpen) ToDispose.Add(BaseStorage);
|
||||
BaseStorage.GetSize(out long baseSize).ThrowIfFailure();
|
||||
Length = baseSize;
|
||||
|
||||
for (int i = 0; i < cacheSize; i++)
|
||||
{
|
||||
|
@ -37,6 +39,9 @@ namespace LibHac.FsSystem
|
|||
long inOffset = offset;
|
||||
int outOffset = 0;
|
||||
|
||||
if (!IsRangeValid(offset, destination.Length, Length))
|
||||
return ResultFs.ValueOutOfRange.Log();
|
||||
|
||||
lock (Blocks)
|
||||
{
|
||||
while (remaining > 0)
|
||||
|
@ -64,6 +69,9 @@ namespace LibHac.FsSystem
|
|||
long inOffset = offset;
|
||||
int outOffset = 0;
|
||||
|
||||
if (!IsRangeValid(offset, source.Length, Length))
|
||||
return ResultFs.ValueOutOfRange.Log();
|
||||
|
||||
lock (Blocks)
|
||||
{
|
||||
while (remaining > 0)
|
||||
|
@ -87,7 +95,7 @@ namespace LibHac.FsSystem
|
|||
return Result.Success;
|
||||
}
|
||||
|
||||
public override Result Flush()
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
lock (Blocks)
|
||||
{
|
||||
|
@ -100,13 +108,13 @@ namespace LibHac.FsSystem
|
|||
return BaseStorage.Flush();
|
||||
}
|
||||
|
||||
public override Result GetSize(out long size)
|
||||
protected override Result GetSizeImpl(out long size)
|
||||
{
|
||||
size = _length;
|
||||
size = Length;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public override Result SetSize(long size)
|
||||
protected override Result SetSizeImpl(long size)
|
||||
{
|
||||
Result rc = BaseStorage.SetSize(size);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
@ -114,11 +122,19 @@ namespace LibHac.FsSystem
|
|||
rc = BaseStorage.GetSize(out long newSize);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
_length = newSize;
|
||||
Length = newSize;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!LeaveOpen)
|
||||
{
|
||||
BaseStorage?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private CacheBlock GetBlock(long blockIndex)
|
||||
{
|
||||
if (BlockDict.TryGetValue(blockIndex, out LinkedListNode<CacheBlock> node))
|
||||
|
@ -157,9 +173,9 @@ namespace LibHac.FsSystem
|
|||
long offset = index * BlockSize;
|
||||
int length = BlockSize;
|
||||
|
||||
if (_length != -1)
|
||||
if (Length != -1)
|
||||
{
|
||||
length = (int)Math.Min(_length - offset, length);
|
||||
length = (int)Math.Min(Length - offset, length);
|
||||
}
|
||||
|
||||
BaseStorage.Read(offset, block.Buffer.AsSpan(0, length)).ThrowIfFailure();
|
||||
|
|
|
@ -7,12 +7,13 @@ namespace LibHac.FsSystem
|
|||
public class ConcatenationStorage : StorageBase
|
||||
{
|
||||
private ConcatSource[] Sources { get; }
|
||||
private long _length;
|
||||
private long Length { get; }
|
||||
private bool LeaveOpen { get; }
|
||||
|
||||
public ConcatenationStorage(IList<IStorage> sources, bool leaveOpen)
|
||||
{
|
||||
Sources = new ConcatSource[sources.Count];
|
||||
if (!leaveOpen) ToDispose.AddRange(sources);
|
||||
LeaveOpen = leaveOpen;
|
||||
|
||||
long length = 0;
|
||||
for (int i = 0; i < sources.Count; i++)
|
||||
|
@ -24,7 +25,7 @@ namespace LibHac.FsSystem
|
|||
length += sourceSize;
|
||||
}
|
||||
|
||||
_length = length;
|
||||
Length = length;
|
||||
}
|
||||
|
||||
protected override Result ReadImpl(long offset, Span<byte> destination)
|
||||
|
@ -32,6 +33,10 @@ namespace LibHac.FsSystem
|
|||
long inPos = offset;
|
||||
int outPos = 0;
|
||||
int remaining = destination.Length;
|
||||
|
||||
if (!IsRangeValid(offset, destination.Length, Length))
|
||||
return ResultFs.ValueOutOfRange.Log();
|
||||
|
||||
int sourceIndex = FindSource(inPos);
|
||||
|
||||
while (remaining > 0)
|
||||
|
@ -59,6 +64,10 @@ namespace LibHac.FsSystem
|
|||
long inPos = offset;
|
||||
int outPos = 0;
|
||||
int remaining = source.Length;
|
||||
|
||||
if (!IsRangeValid(offset, source.Length, Length))
|
||||
return ResultFs.ValueOutOfRange.Log();
|
||||
|
||||
int sourceIndex = FindSource(inPos);
|
||||
|
||||
while (remaining > 0)
|
||||
|
@ -81,7 +90,7 @@ namespace LibHac.FsSystem
|
|||
return Result.Success;
|
||||
}
|
||||
|
||||
public override Result Flush()
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
foreach (ConcatSource source in Sources)
|
||||
{
|
||||
|
@ -92,15 +101,34 @@ namespace LibHac.FsSystem
|
|||
return Result.Success;
|
||||
}
|
||||
|
||||
public override Result GetSize(out long size)
|
||||
protected override Result SetSizeImpl(long size)
|
||||
{
|
||||
size = _length;
|
||||
return ResultFs.NotImplemented.Log();
|
||||
}
|
||||
|
||||
protected override Result GetSizeImpl(out long size)
|
||||
{
|
||||
size = Length;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (!LeaveOpen && Sources != null)
|
||||
{
|
||||
foreach (ConcatSource source in Sources)
|
||||
{
|
||||
source?.Storage?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int FindSource(long offset)
|
||||
{
|
||||
if (offset < 0 || offset >= _length)
|
||||
if (offset < 0 || offset >= Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(offset), offset, "The Storage does not contain this offset.");
|
||||
|
||||
int lo = 0;
|
||||
|
|
|
@ -22,17 +22,17 @@ namespace LibHac.FsSystem
|
|||
return BaseFile.Write(offset, source);
|
||||
}
|
||||
|
||||
public override Result Flush()
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
return BaseFile.Flush();
|
||||
}
|
||||
|
||||
public override Result GetSize(out long size)
|
||||
protected override Result GetSizeImpl(out long size)
|
||||
{
|
||||
return BaseFile.GetSize(out size);
|
||||
}
|
||||
|
||||
public override Result SetSize(long size)
|
||||
protected override Result SetSizeImpl(long size)
|
||||
{
|
||||
return BaseFile.SetSize(size);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,8 @@ namespace LibHac.FsSystem
|
|||
/// </summary>
|
||||
public Validity[][] LevelValidities { get; }
|
||||
|
||||
private long _length;
|
||||
private long Length { get; }
|
||||
private bool LeaveOpen { get; }
|
||||
|
||||
private IntegrityVerificationStorage[] IntegrityStorages { get; }
|
||||
|
||||
|
@ -44,9 +45,10 @@ namespace LibHac.FsSystem
|
|||
}
|
||||
|
||||
DataLevel = Levels[Levels.Length - 1];
|
||||
DataLevel.GetSize(out _length).ThrowIfFailure();
|
||||
DataLevel.GetSize(out long dataSize).ThrowIfFailure();
|
||||
Length = dataSize;
|
||||
|
||||
if (!leaveOpen) ToDispose.Add(DataLevel);
|
||||
LeaveOpen = leaveOpen;
|
||||
}
|
||||
|
||||
public HierarchicalIntegrityVerificationStorage(IvfcHeader header, IStorage masterHash, IStorage data,
|
||||
|
@ -104,17 +106,33 @@ namespace LibHac.FsSystem
|
|||
return DataLevel.Write(offset, source);
|
||||
}
|
||||
|
||||
public override Result Flush()
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
return DataLevel.Flush();
|
||||
}
|
||||
|
||||
public override Result GetSize(out long size)
|
||||
protected override Result SetSizeImpl(long size)
|
||||
{
|
||||
size = _length;
|
||||
return ResultFs.UnsupportedOperationInHierarchicalIvfcStorageSetSize.Log();
|
||||
}
|
||||
|
||||
protected override Result GetSizeImpl(out long size)
|
||||
{
|
||||
size = Length;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (!LeaveOpen)
|
||||
{
|
||||
DataLevel?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks the hashes of any unchecked blocks and returns the <see cref="Validity"/> of the data.
|
||||
/// </summary>
|
||||
|
@ -127,7 +145,7 @@ namespace LibHac.FsSystem
|
|||
IntegrityVerificationStorage storage = IntegrityStorages[IntegrityStorages.Length - 1];
|
||||
|
||||
long blockSize = storage.SectorSize;
|
||||
int blockCount = (int)Util.DivideByRoundUp(_length, blockSize);
|
||||
int blockCount = (int)Util.DivideByRoundUp(Length, blockSize);
|
||||
|
||||
var buffer = new byte[blockSize];
|
||||
var result = Validity.Valid;
|
||||
|
|
|
@ -12,20 +12,21 @@ namespace LibHac.FsSystem
|
|||
|
||||
private List<IStorage> Sources { get; } = new List<IStorage>();
|
||||
private BucketTree<RelocationEntry> BucketTree { get; }
|
||||
private long _length;
|
||||
private long Length { get; }
|
||||
private bool LeaveOpen { get; }
|
||||
|
||||
public IndirectStorage(IStorage bucketTreeData, bool leaveOpen, params IStorage[] sources)
|
||||
{
|
||||
Sources.AddRange(sources);
|
||||
|
||||
if (!leaveOpen) ToDispose.AddRange(sources);
|
||||
LeaveOpen = leaveOpen;
|
||||
|
||||
BucketTree = new BucketTree<RelocationEntry>(bucketTreeData);
|
||||
|
||||
RelocationEntries = BucketTree.GetEntryList();
|
||||
RelocationOffsets = RelocationEntries.Select(x => x.Offset).ToList();
|
||||
|
||||
_length = BucketTree.BucketOffsets.OffsetEnd;
|
||||
Length = BucketTree.BucketOffsets.OffsetEnd;
|
||||
}
|
||||
|
||||
protected override Result ReadImpl(long offset, Span<byte> destination)
|
||||
|
@ -68,22 +69,36 @@ namespace LibHac.FsSystem
|
|||
return ResultFs.UnsupportedOperationInIndirectStorageSetSize.Log();
|
||||
}
|
||||
|
||||
public override Result Flush()
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public override Result GetSize(out long size)
|
||||
protected override Result GetSizeImpl(out long size)
|
||||
{
|
||||
size = _length;
|
||||
size = Length;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public override Result SetSize(long size)
|
||||
protected override Result SetSizeImpl(long size)
|
||||
{
|
||||
return ResultFs.UnsupportedOperationInIndirectStorageSetSize.Log();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (!LeaveOpen && Sources != null)
|
||||
{
|
||||
foreach (IStorage storage in Sources)
|
||||
{
|
||||
storage?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RelocationEntry GetRelocationEntry(long offset)
|
||||
{
|
||||
int index = RelocationOffsets.BinarySearch(offset);
|
||||
|
|
|
@ -124,7 +124,7 @@ namespace LibHac.FsSystem
|
|||
|
||||
public Result Read(long offset, Span<byte> destination, IntegrityCheckLevel integrityCheckLevel)
|
||||
{
|
||||
ValidateParameters(destination, offset);
|
||||
// ValidateParameters(destination, offset);
|
||||
return ReadImpl(offset, destination, integrityCheckLevel);
|
||||
}
|
||||
|
||||
|
@ -188,12 +188,12 @@ namespace LibHac.FsSystem
|
|||
}
|
||||
}
|
||||
|
||||
public override Result Flush()
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
Result rc = HashStorage.Flush();
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return base.Flush();
|
||||
return base.FlushImpl();
|
||||
}
|
||||
|
||||
public void FsTrim()
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using LibHac.Fs;
|
||||
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
|
@ -16,9 +17,6 @@ namespace LibHac.FsSystem
|
|||
Path = path;
|
||||
Stream = new FileStream(Path, mode, access);
|
||||
Storage = new StreamStorage(Stream, false);
|
||||
|
||||
ToDispose.Add(Storage);
|
||||
ToDispose.Add(Stream);
|
||||
}
|
||||
|
||||
protected override Result ReadImpl(long offset, Span<byte> destination)
|
||||
|
@ -31,14 +29,28 @@ namespace LibHac.FsSystem
|
|||
return Storage.Write(offset, source);
|
||||
}
|
||||
|
||||
public override Result Flush()
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
return Storage.Flush();
|
||||
}
|
||||
|
||||
public override Result GetSize(out long size)
|
||||
protected override Result SetSizeImpl(long size)
|
||||
{
|
||||
return ResultFs.NotImplemented.Log();
|
||||
}
|
||||
|
||||
protected override Result GetSizeImpl(out long size)
|
||||
{
|
||||
return Storage.GetSize(out size);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Storage?.Dispose();
|
||||
Stream?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace LibHac.FsSystem
|
|||
private int _length;
|
||||
private int _capacity;
|
||||
private bool _isExpandable;
|
||||
|
||||
|
||||
public MemoryStorage() : this(0) { }
|
||||
|
||||
public MemoryStorage(int capacity)
|
||||
|
@ -19,7 +19,6 @@ namespace LibHac.FsSystem
|
|||
|
||||
_capacity = capacity;
|
||||
_isExpandable = true;
|
||||
CanAutoExpand = true;
|
||||
_buffer = new byte[capacity];
|
||||
}
|
||||
|
||||
|
@ -41,6 +40,9 @@ namespace LibHac.FsSystem
|
|||
|
||||
protected override Result ReadImpl(long offset, Span<byte> destination)
|
||||
{
|
||||
if (!IsRangeValid(offset, destination.Length, _length))
|
||||
return ResultFs.ValueOutOfRange.Log();
|
||||
|
||||
_buffer.AsSpan((int)(_start + offset), destination.Length).CopyTo(destination);
|
||||
|
||||
return Result.Success;
|
||||
|
@ -48,6 +50,9 @@ namespace LibHac.FsSystem
|
|||
|
||||
protected override Result WriteImpl(long offset, ReadOnlySpan<byte> source)
|
||||
{
|
||||
if (!IsRangeValid(offset, source.Length, _length))
|
||||
return ResultFs.ValueOutOfRange.Log();
|
||||
|
||||
long requiredCapacity = _start + offset + source.Length;
|
||||
|
||||
if (requiredCapacity > _length)
|
||||
|
@ -97,15 +102,15 @@ namespace LibHac.FsSystem
|
|||
}
|
||||
}
|
||||
|
||||
public override Result Flush() => Result.Success;
|
||||
protected override Result FlushImpl() => Result.Success;
|
||||
|
||||
public override Result GetSize(out long size)
|
||||
protected override Result GetSizeImpl(out long size)
|
||||
{
|
||||
size = _length;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public override Result SetSize(long size)
|
||||
protected override Result SetSizeImpl(long size)
|
||||
{
|
||||
return ResultFs.UnsupportedOperationInMemoryStorageSetSize.Log();
|
||||
}
|
||||
|
|
|
@ -8,10 +8,11 @@ namespace LibHac.FsSystem
|
|||
/// </summary>
|
||||
public class NullStorage : StorageBase
|
||||
{
|
||||
public NullStorage() { }
|
||||
public NullStorage(long length) => _length = length;
|
||||
private long Length { get; }
|
||||
|
||||
public NullStorage() { }
|
||||
public NullStorage(long length) => Length = length;
|
||||
|
||||
private long _length;
|
||||
|
||||
protected override Result ReadImpl(long offset, Span<byte> destination)
|
||||
{
|
||||
|
@ -24,14 +25,19 @@ namespace LibHac.FsSystem
|
|||
return Result.Success;
|
||||
}
|
||||
|
||||
public override Result Flush()
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public override Result GetSize(out long size)
|
||||
protected override Result SetSizeImpl(long size)
|
||||
{
|
||||
size = _length;
|
||||
return ResultFs.NotImplemented.Log();
|
||||
}
|
||||
|
||||
protected override Result GetSizeImpl(out long size)
|
||||
{
|
||||
size = Length;
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,18 +83,18 @@ namespace LibHac.FsSystem.Save
|
|||
return Result.Success;
|
||||
}
|
||||
|
||||
public override Result Flush()
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
return BaseStorage.Flush();
|
||||
}
|
||||
|
||||
public override Result GetSize(out long size)
|
||||
protected override Result GetSizeImpl(out long size)
|
||||
{
|
||||
size = _length;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public override Result SetSize(long size)
|
||||
protected override Result SetSizeImpl(long size)
|
||||
{
|
||||
int oldBlockCount = (int)Util.DivideByRoundUp(_length, BlockSize);
|
||||
int newBlockCount = (int)Util.DivideByRoundUp(size, BlockSize);
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace LibHac.FsSystem.Save
|
|||
private IStorage DataB { get; }
|
||||
private DuplexBitmap Bitmap { get; }
|
||||
|
||||
private long _length;
|
||||
private long Length { get; }
|
||||
|
||||
public DuplexStorage(IStorage dataA, IStorage dataB, IStorage bitmap, int blockSize)
|
||||
{
|
||||
|
@ -23,7 +23,8 @@ namespace LibHac.FsSystem.Save
|
|||
bitmap.GetSize(out long bitmapSize).ThrowIfFailure();
|
||||
|
||||
Bitmap = new DuplexBitmap(BitmapStorage, (int)(bitmapSize * 8));
|
||||
DataA.GetSize(out _length).ThrowIfFailure();
|
||||
DataA.GetSize(out long dataSize).ThrowIfFailure();
|
||||
Length = dataSize;
|
||||
}
|
||||
|
||||
protected override Result ReadImpl(long offset, Span<byte> destination)
|
||||
|
@ -32,6 +33,9 @@ namespace LibHac.FsSystem.Save
|
|||
int outPos = 0;
|
||||
int remaining = destination.Length;
|
||||
|
||||
if (!IsRangeValid(offset, destination.Length, Length))
|
||||
return ResultFs.ValueOutOfRange.Log();
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
int blockNum = (int)(inPos / BlockSize);
|
||||
|
@ -58,6 +62,9 @@ namespace LibHac.FsSystem.Save
|
|||
int outPos = 0;
|
||||
int remaining = source.Length;
|
||||
|
||||
if (!IsRangeValid(offset, source.Length, Length))
|
||||
return ResultFs.ValueOutOfRange.Log();
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
int blockNum = (int)(inPos / BlockSize);
|
||||
|
@ -78,7 +85,7 @@ namespace LibHac.FsSystem.Save
|
|||
return Result.Success;
|
||||
}
|
||||
|
||||
public override Result Flush()
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
Result rc = BitmapStorage.Flush();
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
@ -92,9 +99,14 @@ namespace LibHac.FsSystem.Save
|
|||
return Result.Success;
|
||||
}
|
||||
|
||||
public override Result GetSize(out long size)
|
||||
protected override Result SetSizeImpl(long size)
|
||||
{
|
||||
size = _length;
|
||||
return ResultFs.NotImplemented.Log();
|
||||
}
|
||||
|
||||
protected override Result GetSizeImpl(out long size)
|
||||
{
|
||||
size = Length;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace LibHac.FsSystem.Save
|
|||
{
|
||||
private DuplexStorage[] Layers { get; }
|
||||
private DuplexStorage DataLayer { get; }
|
||||
private long _length;
|
||||
private long Length { get; }
|
||||
|
||||
public HierarchicalDuplexStorage(DuplexFsLayerInfo[] layers, bool masterBit)
|
||||
{
|
||||
|
@ -30,7 +30,8 @@ namespace LibHac.FsSystem.Save
|
|||
}
|
||||
|
||||
DataLayer = Layers[Layers.Length - 1];
|
||||
DataLayer.GetSize(out _length).ThrowIfFailure();
|
||||
DataLayer.GetSize(out long dataSize).ThrowIfFailure();
|
||||
Length = dataSize;
|
||||
}
|
||||
|
||||
protected override Result ReadImpl(long offset, Span<byte> destination)
|
||||
|
@ -43,14 +44,19 @@ namespace LibHac.FsSystem.Save
|
|||
return DataLayer.Write(offset, source);
|
||||
}
|
||||
|
||||
public override Result Flush()
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
return DataLayer.Flush();
|
||||
}
|
||||
|
||||
public override Result GetSize(out long size)
|
||||
protected override Result SetSizeImpl(long size)
|
||||
{
|
||||
size = _length;
|
||||
return ResultFs.NotImplemented.Log();
|
||||
}
|
||||
|
||||
protected override Result GetSizeImpl(out long size)
|
||||
{
|
||||
size = Length;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,8 @@ namespace LibHac.FsSystem.Save
|
|||
|
||||
public int BlockSize { get; }
|
||||
|
||||
private long _length;
|
||||
private long Length { get; }
|
||||
private bool LeaveOpen { get; }
|
||||
|
||||
public JournalStorage(IStorage baseStorage, IStorage header, JournalMapParams mapInfo, bool leaveOpen)
|
||||
{
|
||||
|
@ -27,9 +28,9 @@ namespace LibHac.FsSystem.Save
|
|||
Map = new JournalMap(mapHeader, mapInfo);
|
||||
|
||||
BlockSize = (int)Header.BlockSize;
|
||||
_length = Header.TotalSize - Header.JournalSize;
|
||||
Length = Header.TotalSize - Header.JournalSize;
|
||||
|
||||
if (!leaveOpen) ToDispose.Add(baseStorage);
|
||||
LeaveOpen = leaveOpen;
|
||||
}
|
||||
|
||||
protected override Result ReadImpl(long offset, Span<byte> destination)
|
||||
|
@ -38,6 +39,9 @@ namespace LibHac.FsSystem.Save
|
|||
int outPos = 0;
|
||||
int remaining = destination.Length;
|
||||
|
||||
if (!IsRangeValid(offset, destination.Length, Length))
|
||||
return ResultFs.ValueOutOfRange.Log();
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
int blockNum = (int)(inPos / BlockSize);
|
||||
|
@ -64,6 +68,9 @@ namespace LibHac.FsSystem.Save
|
|||
int outPos = 0;
|
||||
int remaining = source.Length;
|
||||
|
||||
if (!IsRangeValid(offset, source.Length, Length))
|
||||
return ResultFs.ValueOutOfRange.Log();
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
int blockNum = (int)(inPos / BlockSize);
|
||||
|
@ -84,17 +91,33 @@ namespace LibHac.FsSystem.Save
|
|||
return Result.Success;
|
||||
}
|
||||
|
||||
public override Result Flush()
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
return BaseStorage.Flush();
|
||||
}
|
||||
|
||||
public override Result GetSize(out long size)
|
||||
protected override Result SetSizeImpl(long size)
|
||||
{
|
||||
size = _length;
|
||||
return ResultFs.NotImplemented.Log();
|
||||
}
|
||||
|
||||
protected override Result GetSizeImpl(out long size)
|
||||
{
|
||||
size = Length;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (!LeaveOpen)
|
||||
{
|
||||
BaseStorage?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IStorage GetBaseStorage() => BaseStorage.AsReadOnly();
|
||||
public IStorage GetHeaderStorage() => HeaderStorage.AsReadOnly();
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ namespace LibHac.FsSystem.Save
|
|||
private IStorage BaseStorage { get; }
|
||||
private IStorage HeaderStorage { get; }
|
||||
private IStorage MapEntryStorage { get; }
|
||||
private bool LeaveOpen { get; }
|
||||
|
||||
private RemapHeader Header { get; }
|
||||
public MapEntry[] MapEntries { get; set; }
|
||||
|
@ -42,7 +43,7 @@ namespace LibHac.FsSystem.Save
|
|||
MapEntries[i] = new MapEntry(reader);
|
||||
}
|
||||
|
||||
if (!leaveOpen) ToDispose.Add(BaseStorage);
|
||||
LeaveOpen = leaveOpen;
|
||||
|
||||
Segments = InitSegments(Header, MapEntries);
|
||||
}
|
||||
|
@ -109,18 +110,34 @@ namespace LibHac.FsSystem.Save
|
|||
return Result.Success;
|
||||
}
|
||||
|
||||
public override Result Flush()
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
return BaseStorage.Flush();
|
||||
}
|
||||
|
||||
public override Result GetSize(out long size)
|
||||
protected override Result SetSizeImpl(long size)
|
||||
{
|
||||
return ResultFs.UnsupportedOperationInHierarchicalIvfcStorageSetSize.Log();
|
||||
}
|
||||
|
||||
protected override Result GetSizeImpl(out long size)
|
||||
{
|
||||
// todo: Different result code
|
||||
size = -1;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (!LeaveOpen)
|
||||
{
|
||||
BaseStorage?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IStorage GetBaseStorage() => BaseStorage.AsReadOnly();
|
||||
public IStorage GetHeaderStorage() => HeaderStorage.AsReadOnly();
|
||||
public IStorage GetMapEntryStorage() => MapEntryStorage.AsReadOnly();
|
||||
|
|
|
@ -10,7 +10,8 @@ namespace LibHac.FsSystem
|
|||
public int SectorSize { get; }
|
||||
public int SectorCount { get; private set; }
|
||||
|
||||
private long _length;
|
||||
private long Length { get; set; }
|
||||
private bool LeaveOpen { get; }
|
||||
|
||||
public SectorStorage(IStorage baseStorage, int sectorSize, bool leaveOpen)
|
||||
{
|
||||
|
@ -20,9 +21,9 @@ namespace LibHac.FsSystem
|
|||
baseStorage.GetSize(out long baseSize).ThrowIfFailure();
|
||||
|
||||
SectorCount = (int)Util.DivideByRoundUp(baseSize, SectorSize);
|
||||
_length = baseSize;
|
||||
Length = baseSize;
|
||||
|
||||
if (!leaveOpen) ToDispose.Add(BaseStorage);
|
||||
LeaveOpen = leaveOpen;
|
||||
}
|
||||
|
||||
protected override Result ReadImpl(long offset, Span<byte> destination)
|
||||
|
@ -37,18 +38,18 @@ namespace LibHac.FsSystem
|
|||
return BaseStorage.Write(offset, source);
|
||||
}
|
||||
|
||||
public override Result Flush()
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
return BaseStorage.Flush();
|
||||
}
|
||||
|
||||
public override Result GetSize(out long size)
|
||||
protected override Result GetSizeImpl(out long size)
|
||||
{
|
||||
size = _length;
|
||||
size = Length;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public override Result SetSize(long size)
|
||||
protected override Result SetSizeImpl(long size)
|
||||
{
|
||||
Result rc = BaseStorage.SetSize(size);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
@ -57,11 +58,22 @@ namespace LibHac.FsSystem
|
|||
if (rc.IsFailure()) return rc;
|
||||
|
||||
SectorCount = (int)Util.DivideByRoundUp(newSize, SectorSize);
|
||||
_length = newSize;
|
||||
Length = newSize;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (!LeaveOpen)
|
||||
{
|
||||
BaseStorage?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that the size is a multiple of the sector size
|
||||
/// </summary>
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using LibHac.Fs;
|
||||
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
public abstract class StorageBase : IStorage
|
||||
{
|
||||
private bool _isDisposed;
|
||||
protected internal List<IDisposable> ToDispose { get; } = new List<IDisposable>();
|
||||
protected bool CanAutoExpand { get; set; }
|
||||
|
||||
protected abstract Result ReadImpl(long offset, Span<byte> destination);
|
||||
protected abstract Result WriteImpl(long offset, ReadOnlySpan<byte> source);
|
||||
public abstract Result Flush();
|
||||
public abstract Result GetSize(out long size);
|
||||
|
||||
public Result Read(long offset, Span<byte> destination)
|
||||
{
|
||||
ValidateParameters(destination, offset);
|
||||
return ReadImpl(offset, destination);
|
||||
}
|
||||
|
||||
public Result Write(long offset, ReadOnlySpan<byte> source)
|
||||
{
|
||||
ValidateParameters(source, offset);
|
||||
return WriteImpl(offset, source);
|
||||
}
|
||||
|
||||
public virtual Result SetSize(long size)
|
||||
{
|
||||
return ResultFs.NotImplemented.Log();
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
Flush();
|
||||
foreach (IDisposable item in ToDispose)
|
||||
{
|
||||
item?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected void ValidateParameters(ReadOnlySpan<byte> span, long offset)
|
||||
{
|
||||
if (_isDisposed) throw new ObjectDisposedException(null);
|
||||
if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), "Argument must be non-negative.");
|
||||
|
||||
Result sizeResult = GetSize(out long length);
|
||||
sizeResult.ThrowIfFailure();
|
||||
|
||||
if (length != -1 && !CanAutoExpand)
|
||||
{
|
||||
if (offset + span.Length > length) throw new ArgumentException("The given offset and count exceed the length of the Storage");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using LibHac.Fs;
|
||||
|
||||
#if !STREAM_SPAN
|
||||
using System.Buffers;
|
||||
|
@ -13,13 +14,14 @@ namespace LibHac.FsSystem
|
|||
|
||||
private Stream BaseStream { get; }
|
||||
private object Locker { get; } = new object();
|
||||
private long _length;
|
||||
private long Length { get; }
|
||||
private bool LeaveOpen { get; }
|
||||
|
||||
public StreamStorage(Stream baseStream, bool leaveOpen)
|
||||
{
|
||||
BaseStream = baseStream;
|
||||
_length = BaseStream.Length;
|
||||
if (!leaveOpen) ToDispose.Add(BaseStream);
|
||||
Length = BaseStream.Length;
|
||||
LeaveOpen = leaveOpen;
|
||||
}
|
||||
|
||||
protected override Result ReadImpl(long offset, Span<byte> destination)
|
||||
|
@ -90,7 +92,7 @@ namespace LibHac.FsSystem
|
|||
return Result.Success;
|
||||
}
|
||||
|
||||
public override Result Flush()
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
|
@ -100,10 +102,26 @@ namespace LibHac.FsSystem
|
|||
}
|
||||
}
|
||||
|
||||
public override Result GetSize(out long size)
|
||||
protected override Result SetSizeImpl(long size)
|
||||
{
|
||||
size = _length;
|
||||
return ResultFs.NotImplemented.Log();
|
||||
}
|
||||
|
||||
protected override Result GetSizeImpl(out long size)
|
||||
{
|
||||
size = Length;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (!LeaveOpen)
|
||||
{
|
||||
BaseStream?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,26 +9,27 @@ namespace LibHac.FsSystem
|
|||
private IStorage BaseStorage { get; }
|
||||
private long Offset { get; }
|
||||
private FileAccess Access { get; } = FileAccess.ReadWrite;
|
||||
private long _length;
|
||||
private long Length { get; set; }
|
||||
private bool LeaveOpen { get; }
|
||||
|
||||
public SubStorage(IStorage baseStorage, long offset, long length)
|
||||
{
|
||||
BaseStorage = baseStorage;
|
||||
Offset = offset;
|
||||
_length = length;
|
||||
Length = length;
|
||||
}
|
||||
|
||||
public SubStorage(SubStorage baseStorage, long offset, long length)
|
||||
{
|
||||
BaseStorage = baseStorage.BaseStorage;
|
||||
Offset = baseStorage.Offset + offset;
|
||||
_length = length;
|
||||
Length = length;
|
||||
}
|
||||
|
||||
public SubStorage(IStorage baseStorage, long offset, long length, bool leaveOpen)
|
||||
: this(baseStorage, offset, length)
|
||||
{
|
||||
if (!leaveOpen) ToDispose.Add(BaseStorage);
|
||||
LeaveOpen = leaveOpen;
|
||||
}
|
||||
|
||||
public SubStorage(IStorage baseStorage, long offset, long length, bool leaveOpen, FileAccess access)
|
||||
|
@ -49,18 +50,18 @@ namespace LibHac.FsSystem
|
|||
return BaseStorage.Write(offset + Offset, source);
|
||||
}
|
||||
|
||||
public override Result Flush()
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
return BaseStorage.Flush();
|
||||
}
|
||||
|
||||
public override Result GetSize(out long size)
|
||||
protected override Result GetSizeImpl(out long size)
|
||||
{
|
||||
size = _length;
|
||||
size = Length;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public override Result SetSize(long size)
|
||||
protected override Result SetSizeImpl(long size)
|
||||
{
|
||||
if (BaseStorage == null) return ResultFs.SubStorageNotInitialized.Log();
|
||||
|
||||
|
@ -72,7 +73,7 @@ namespace LibHac.FsSystem
|
|||
Result rc = BaseStorage.GetSize(out long baseSize);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (baseSize != Offset + _length)
|
||||
if (baseSize != Offset + Length)
|
||||
{
|
||||
// SubStorage cannot be resized unless it is located at the end of the base storage.
|
||||
return ResultFs.SubStorageNotResizableMiddleOfFile.Log();
|
||||
|
@ -81,9 +82,20 @@ namespace LibHac.FsSystem
|
|||
rc = BaseStorage.SetSize(Offset + size);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
_length = size;
|
||||
Length = size;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (!LeaveOpen)
|
||||
{
|
||||
BaseStorage?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue