diff --git a/src/LibHac/Fs/FsEnums.cs b/src/LibHac/Fs/FsEnums.cs index 981f5fdd..5582f5a8 100644 --- a/src/LibHac/Fs/FsEnums.cs +++ b/src/LibHac/Fs/FsEnums.cs @@ -132,13 +132,6 @@ namespace LibHac.Fs None = 0 } - [Flags] - public enum WriteOptionFlag - { - None = 0, - Flush = 1 - } - public enum OperationId { Clear = 0, diff --git a/src/LibHac/Fs/Fsa/IFile2.cs b/src/LibHac/Fs/Fsa/IFile2.cs new file mode 100644 index 00000000..d0251f1a --- /dev/null +++ b/src/LibHac/Fs/Fsa/IFile2.cs @@ -0,0 +1,179 @@ +using System; +using LibHac.Diag; + +namespace LibHac.Fs.Fsa +{ + // ReSharper disable once InconsistentNaming + public abstract class IFile2 : IDisposable + { + public Result Read(out long bytesRead, long offset, Span destination, in ReadOption option) + { + if (destination.IsEmpty) + { + bytesRead = 0; + return Result.Success; + } + + if (offset < 0) + { + bytesRead = 0; + return ResultFs.OutOfRange.Log(); + } + + if (long.MaxValue - offset < destination.Length) + { + bytesRead = 0; + return ResultFs.OutOfRange.Log(); + } + + return DoRead(out bytesRead, offset, destination, in option); + } + + public Result Read(out long bytesRead, long offset, Span destination) + { + return Read(out bytesRead, offset, destination, ReadOption.None); + } + + public Result Write(long offset, ReadOnlySpan source, in WriteOption option) + { + if (source.IsEmpty) + { + if (option.HasFlushFlag()) + { + Result rc = Flush(); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + + if (offset < 0) + return ResultFs.OutOfRange.Log(); + + if (long.MaxValue - offset < source.Length) + return ResultFs.OutOfRange.Log(); + + return DoWrite(offset, source, in option); + } + + public Result Flush() + { + return DoFlush(); + } + + public Result SetSize(long size) + { + if (size < 0) + return ResultFs.OutOfRange.Log(); + + return DoSetSize(size); + } + + public Result GetSize(out long size) + { + return DoGetSize(out size); + } + + public Result OperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + return DoOperateRange(outBuffer, operationId, offset, size, inBuffer); + } + + public Result OperateRange(OperationId operationId, long offset, long size) + { + return DoOperateRange(Span.Empty, operationId, offset, size, ReadOnlySpan.Empty); + } + + protected Result DryRead(out long readableBytes, long offset, long size, in ReadOption option, + OpenMode openMode) + { + // Check that we can read. + if (!openMode.HasFlag(OpenMode.Read)) + { + readableBytes = default; + return ResultFs.InvalidOpenModeForRead.Log(); + } + + // Get the file size, and validate our offset. + Result rc = GetSize(out long fileSize); + if (rc.IsFailure()) + { + readableBytes = default; + return rc; + } + + if (offset > fileSize) + { + readableBytes = default; + return ResultFs.OutOfRange.Log(); + } + + readableBytes = Math.Min(fileSize - offset, size); + return Result.Success; + } + + protected Result DrySetSize(long size, OpenMode openMode) + { + // Check that we can write. + if (!openMode.HasFlag(OpenMode.Write)) + return ResultFs.InvalidOpenModeForWrite.Log(); + + Assert.AssertTrue(size >= 0); + + return Result.Success; + } + + protected Result DryWrite(out bool needsAppend, long offset, long size, in WriteOption option, + OpenMode openMode) + { + // Check that we can write. + if (!openMode.HasFlag(OpenMode.Write)) + { + needsAppend = default; + return ResultFs.InvalidOpenModeForWrite.Log(); + } + + // Get the file size. + Result rc = GetSize(out long fileSize); + if (rc.IsFailure()) + { + needsAppend = default; + return rc; + } + + if (fileSize < offset + size) + { + if (!openMode.HasFlag(OpenMode.AllowAppend)) + { + needsAppend = default; + return ResultFs.FileExtensionWithoutOpenModeAllowAppend.Log(); + } + + needsAppend = true; + } + else + { + needsAppend = false; + } + + return Result.Success; + } + + protected abstract Result DoRead(out long bytesRead, long offset, Span destination, in ReadOption option); + protected abstract Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option); + protected abstract Result DoFlush(); + protected abstract Result DoSetSize(long size); + protected abstract Result DoGetSize(out long size); + protected abstract Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer); + + protected virtual void Dispose(bool disposing) { } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/LibHac/Fs/IFile2.cs b/src/LibHac/Fs/IFile2.cs new file mode 100644 index 00000000..567826fc --- /dev/null +++ b/src/LibHac/Fs/IFile2.cs @@ -0,0 +1,39 @@ +using System; + +namespace LibHac.Fs +{ + public readonly struct ReadOption + { + public readonly int Value; + + // ReSharper disable once UnusedMember.Local + private ReadOption(int value) + { + Value = value; + } + + public static ReadOption None => default; + } + + public readonly struct WriteOption + { + public readonly WriteOptionFlag Flags; + + private WriteOption(WriteOptionFlag flags) + { + Flags = flags; + } + + public bool HasFlushFlag() => Flags.HasFlag(WriteOptionFlag.Flush); + + public static WriteOption None => default; + public static WriteOption Flush => new WriteOption(WriteOptionFlag.Flush); + } + + [Flags] + public enum WriteOptionFlag + { + None = 0, + Flush = 1 + } +}