diff --git a/build/CodeGen/results.csv b/build/CodeGen/results.csv
index 3adccd8c..677019fc 100644
--- a/build/CodeGen/results.csv
+++ b/build/CodeGen/results.csv
@@ -199,8 +199,8 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary
2,6203,,InvalidOpenModeForWrite,
2,6300,6399,UnsupportedOperation,
-2,6302,,SubStorageNotResizable,
-2,6303,,SubStorageNotResizableMiddleOfFile,
+2,6302,,UnsupportedOperationInSubStorageSetSize,Attempted to resize a non-resizable SubStorage.
+2,6303,,UnsupportedOperationInResizableSubStorageSetSize,Attempted to resize a SubStorage that wasn't located at the end of the base storage.
2,6304,,UnsupportedOperationInMemoryStorageSetSize,
2,6306,,UnsupportedOperationInFileStorageOperateRange,
2,6310,,UnsupportedOperationInAesCtrExStorageWrite,
@@ -247,7 +247,7 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary
2,6811,,RemapStorageMapFull,
2,6900,6999,BadState,
-2,6902,,SubStorageNotInitialized,
+2,6902,,NotInitialized,
2,6905,,NotMounted,
2,6906,,SaveDataIsExtending,
diff --git a/src/LibHac/Fs/ResultFs.cs b/src/LibHac/Fs/ResultFs.cs
index a1bf0ae9..4fa72bd5 100644
--- a/src/LibHac/Fs/ResultFs.cs
+++ b/src/LibHac/Fs/ResultFs.cs
@@ -385,10 +385,10 @@ namespace LibHac.Fs
/// Error code: 2002-6300; Range: 6300-6399; Inner value: 0x313802
public static Result.Base UnsupportedOperation { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6300, 6399); }
- /// Error code: 2002-6302; Inner value: 0x313c02
- public static Result.Base SubStorageNotResizable => new Result.Base(ModuleFs, 6302);
- /// Error code: 2002-6303; Inner value: 0x313e02
- public static Result.Base SubStorageNotResizableMiddleOfFile => new Result.Base(ModuleFs, 6303);
+ /// Attempted to resize a non-resizable SubStorage.
Error code: 2002-6302; Inner value: 0x313c02
+ public static Result.Base UnsupportedOperationInSubStorageSetSize => new Result.Base(ModuleFs, 6302);
+ /// Attempted to resize a SubStorage that wasn't located at the end of the base storage.
Error code: 2002-6303; Inner value: 0x313e02
+ public static Result.Base UnsupportedOperationInResizableSubStorageSetSize => new Result.Base(ModuleFs, 6303);
/// Error code: 2002-6304; Inner value: 0x314002
public static Result.Base UnsupportedOperationInMemoryStorageSetSize => new Result.Base(ModuleFs, 6304);
/// Error code: 2002-6306; Inner value: 0x314402
@@ -476,7 +476,7 @@ namespace LibHac.Fs
/// Error code: 2002-6900; Range: 6900-6999; Inner value: 0x35e802
public static Result.Base BadState { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6900, 6999); }
/// Error code: 2002-6902; Inner value: 0x35ec02
- public static Result.Base SubStorageNotInitialized => new Result.Base(ModuleFs, 6902);
+ public static Result.Base NotInitialized => new Result.Base(ModuleFs, 6902);
/// Error code: 2002-6905; Inner value: 0x35f202
public static Result.Base NotMounted => new Result.Base(ModuleFs, 6905);
/// Error code: 2002-6906; Inner value: 0x35f402
diff --git a/src/LibHac/Fs/SubStorage2.cs b/src/LibHac/Fs/SubStorage2.cs
index 3f459d7d..22099adb 100644
--- a/src/LibHac/Fs/SubStorage2.cs
+++ b/src/LibHac/Fs/SubStorage2.cs
@@ -1,31 +1,64 @@
using System;
+using LibHac.Diag;
namespace LibHac.Fs
{
public class SubStorage2 : IStorage
{
+ private ReferenceCountedDisposable SharedBaseStorage { get; set; }
private IStorage BaseStorage { get; }
private long Offset { get; }
private long Size { get; set; }
- public bool IsResizable { get; set; }
+ private bool IsResizable { get; set; }
public SubStorage2(IStorage baseStorage, long offset, long size)
{
BaseStorage = baseStorage;
Offset = offset;
Size = size;
+ IsResizable = false;
+
+ Assert.AssertTrue(IsValid());
+ Assert.AssertTrue(Offset >= 0);
+ Assert.AssertTrue(Size >= 0);
}
- public SubStorage2(SubStorage2 baseStorage, long offset, long size)
+ public SubStorage2(ReferenceCountedDisposable sharedBaseStorage, long offset, long size)
{
- BaseStorage = baseStorage.BaseStorage;
- Offset = baseStorage.Offset + offset;
+ SharedBaseStorage = sharedBaseStorage.AddReference();
+ BaseStorage = SharedBaseStorage.Target;
+ Offset = offset;
Size = size;
+ IsResizable = false;
+
+ Assert.AssertTrue(IsValid());
+ Assert.AssertTrue(Offset >= 0);
+ Assert.AssertTrue(Size >= 0);
+ }
+
+ public SubStorage2(SubStorage2 subStorage, long offset, long size)
+ {
+ BaseStorage = subStorage.BaseStorage;
+ Offset = subStorage.Offset + offset;
+ Size = size;
+ IsResizable = false;
+
+ Assert.AssertTrue(IsValid());
+ Assert.AssertTrue(Offset >= 0);
+ Assert.AssertTrue(Size >= 0);
+ Assert.AssertTrue(subStorage.Size >= offset + size);
+ }
+
+ private bool IsValid() => BaseStorage != null;
+
+ public void SetResizable(bool isResizable)
+ {
+ IsResizable = isResizable;
}
protected override Result DoRead(long offset, Span destination)
{
- if (BaseStorage == null) return ResultFs.SubStorageNotInitialized.Log();
+ if (!IsValid()) return ResultFs.NotInitialized.Log();
if (destination.Length == 0) return Result.Success;
if (!IsRangeValid(offset, destination.Length, Size)) return ResultFs.OutOfRange.Log();
@@ -35,7 +68,7 @@ namespace LibHac.Fs
protected override Result DoWrite(long offset, ReadOnlySpan source)
{
- if (BaseStorage == null) return ResultFs.SubStorageNotInitialized.Log();
+ if (!IsValid()) return ResultFs.NotInitialized.Log();
if (source.Length == 0) return Result.Success;
if (!IsRangeValid(offset, source.Length, Size)) return ResultFs.OutOfRange.Log();
@@ -45,24 +78,24 @@ namespace LibHac.Fs
protected override Result DoFlush()
{
- if (BaseStorage == null) return ResultFs.SubStorageNotInitialized.Log();
+ if (!IsValid()) return ResultFs.NotInitialized.Log();
return BaseStorage.Flush();
}
protected override Result DoSetSize(long size)
{
- if (BaseStorage == null) return ResultFs.SubStorageNotInitialized.Log();
- if (!IsResizable) return ResultFs.SubStorageNotResizable.Log();
- if (size < 0 || Offset < 0) return ResultFs.InvalidSize.Log();
+ if (!IsValid()) return ResultFs.NotInitialized.Log();
+ if (!IsResizable) return ResultFs.UnsupportedOperationInSubStorageSetSize.Log();
+ if (!IsOffsetAndSizeValid(Offset, size)) return ResultFs.InvalidSize.Log();
- Result rc = BaseStorage.GetSize(out long baseSize);
+ Result rc = BaseStorage.GetSize(out long currentSize);
if (rc.IsFailure()) return rc;
- if (baseSize != Offset + Size)
+ if (currentSize != Offset + Size)
{
// SubStorage cannot be resized unless it is located at the end of the base storage.
- return ResultFs.SubStorageNotResizableMiddleOfFile.Log();
+ return ResultFs.UnsupportedOperationInResizableSubStorageSetSize.Log();
}
rc = BaseStorage.SetSize(Offset + size);
@@ -76,10 +109,32 @@ namespace LibHac.Fs
{
size = default;
- if (BaseStorage == null) return ResultFs.SubStorageNotInitialized.Log();
+ if (!IsValid()) return ResultFs.NotInitialized.Log();
size = Size;
return Result.Success;
}
+
+ protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer)
+ {
+ if (!IsValid()) return ResultFs.NotInitialized.Log();
+
+ if (size == 0) return Result.Success;
+
+ if (!IsOffsetAndSizeValid(Offset, size)) return ResultFs.OutOfRange.Log();
+
+ return base.DoOperateRange(outBuffer, operationId, Offset + offset, size, inBuffer);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ SharedBaseStorage?.Dispose();
+ SharedBaseStorage = null;
+ }
+
+ base.Dispose(disposing);
+ }
}
}
diff --git a/src/LibHac/FsSystem/SubStorage.cs b/src/LibHac/FsSystem/SubStorage.cs
index 90991dc7..7a287bc1 100644
--- a/src/LibHac/FsSystem/SubStorage.cs
+++ b/src/LibHac/FsSystem/SubStorage.cs
@@ -63,7 +63,7 @@ namespace LibHac.FsSystem
protected override Result DoSetSize(long size)
{
- if (BaseStorage == null) return ResultFs.SubStorageNotInitialized.Log();
+ if (BaseStorage == null) return ResultFs.NotInitialized.Log();
// todo: Add IsResizable member
// if (!IsResizable) return ResultFs.SubStorageNotResizable.Log();
@@ -76,7 +76,7 @@ namespace LibHac.FsSystem
if (baseSize != Offset + Length)
{
// SubStorage cannot be resized unless it is located at the end of the base storage.
- return ResultFs.SubStorageNotResizableMiddleOfFile.Log();
+ return ResultFs.UnsupportedOperationInResizableSubStorageSetSize.Log();
}
rc = BaseStorage.SetSize(Offset + size);
diff --git a/src/LibHac/ReferenceCountedDisposable.cs b/src/LibHac/ReferenceCountedDisposable.cs
new file mode 100644
index 00000000..5943417a
--- /dev/null
+++ b/src/LibHac/ReferenceCountedDisposable.cs
@@ -0,0 +1,282 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// This file was taken from the Roslyn repository
+// https://github.com/dotnet/roslyn/blob/55c7d2f800eff819198dc76892cb4e7574d563c8/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ReferenceCountedDisposable.cs
+
+#nullable enable
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace LibHac
+{
+ ///
+ /// A reference-counting wrapper which allows multiple uses of a single disposable object in code, which is
+ /// deterministically released (by calling ) when the last reference is
+ /// disposed.
+ ///
+ ///
+ /// Each instance of represents a counted reference (also
+ /// referred to as a reference in the following documentation) to a target object. Each of these
+ /// references has a lifetime, starting when it is constructed and continuing through its release. During
+ /// this time, the reference is considered alive. Each reference which is alive owns exactly one
+ /// reference to the target object, ensuring that it will not be disposed while still in use. A reference is
+ /// released through either of the following actions:
+ ///
+ ///
+ /// - The reference is explicitly released by a call to .
+ /// - The reference is no longer in use by managed code and gets reclaimed by the garbage collector.
+ ///
+ ///
+ /// While each instance of should be explicitly disposed when
+ /// the object is no longer needed by the code owning the reference, this implementation will not leak resources
+ /// in the event one or more callers fail to do so. When all references to an object are explicitly released
+ /// (i.e. by calling ), the target object will itself be deterministically released by a
+ /// call to when the last reference to it is released. However, in the event
+ /// one or more references is not explicitly released, the underlying object will still become eligible for
+ /// non-deterministic release (i.e. finalization) as soon as each reference to it is released by one of the
+ /// two actions described previously.
+ ///
+ /// When using , certain steps must be taken to ensure the
+ /// target object is not disposed early.
+ ///
+ ///
+ /// Use consistently. In other words, do not mix code using
+ /// reference-counted wrappers with code that references to the target directly.
+ /// Only use the constructor one time per target object.
+ /// Additional references to the same target object must only be obtained by calling
+ /// .
+ /// Do not call on the target object directly. It will be called
+ /// automatically at the appropriate time, as described above.
+ ///
+ ///
+ /// All public methods on this type adhere to their pre- and post-conditions and will not invalidate state
+ /// even in concurrent execution.
+ ///
+ /// The type of disposable object.
+ public sealed class ReferenceCountedDisposable : IDisposable
+ where T : class, IDisposable
+ {
+ ///
+ /// The target of this reference. This value is initialized to a non- value in the
+ /// constructor, and set to when the current reference is disposed.
+ ///
+ ///
+ /// This value is only cleared in order to support cases where one or more references is garbage
+ /// collected without having called.
+ ///
+ private T? _instance;
+
+ ///
+ /// The boxed reference count, which is shared by all references with the same object.
+ ///
+ ///
+ /// This field serves as the synchronization object for the current type, since it is shared among all
+ /// counted reference to the same target object. Accesses to should only
+ /// occur when this object is locked.
+ ///
+ /// PERF DEV NOTE: A concurrent (but complex) implementation of this type with identical semantics is
+ /// available in source control history. The use of exclusive locks was not causing any measurable
+ /// performance overhead even on 28-thread machines at the time this was written.
+ ///
+ private readonly StrongBox _boxedReferenceCount;
+
+ ///
+ /// Initializes a new reference counting wrapper around an object.
+ ///
+ ///
+ /// The reference count is initialized to 1.
+ ///
+ /// The object owned by this wrapper.
+ ///
+ /// If is .
+ ///
+ public ReferenceCountedDisposable(T instance)
+ : this(instance, new StrongBox(1))
+ {
+ }
+
+ private ReferenceCountedDisposable(T instance, StrongBox referenceCount)
+ {
+ _instance = instance ?? throw new ArgumentNullException(nameof(instance));
+
+ // The reference count has already been incremented for this instance
+ _boxedReferenceCount = referenceCount;
+ }
+
+ ///
+ /// Gets the target object.
+ ///
+ ///
+ /// This call is not valid after is called. If this property or the target
+ /// object is used concurrently with a call to , it is possible for the code to be
+ /// using a disposed object. After the current instance is disposed, this property throws
+ /// . However, the exact time when this property starts throwing after
+ /// is called is unspecified; code is expected to not use this property or the object
+ /// it returns after any code invokes .
+ ///
+ /// The target object.
+ public T Target => _instance ?? throw new ObjectDisposedException(nameof(ReferenceCountedDisposable));
+
+ ///
+ /// Increments the reference count for the disposable object, and returns a new disposable reference to it.
+ ///
+ ///
+ /// The returned object is an independent reference to the same underlying object. Disposing of the
+ /// returned value multiple times will only cause the reference count to be decreased once.
+ ///
+ /// A new pointing to the same underlying object, if it
+ /// has not yet been disposed; otherwise, is thrown if this reference
+ /// to the underlying object has already been disposed.
+ public ReferenceCountedDisposable AddReference()
+ => TryAddReferenceImpl(_instance, _boxedReferenceCount) ??
+ throw new ObjectDisposedException(nameof(ReferenceCountedDisposable));
+
+ ///
+ /// Increments the reference count for the disposable object, and returns a new disposable reference to it.
+ ///
+ ///
+ /// The returned object is an independent reference to the same underlying object. Disposing of the
+ /// returned value multiple times will only cause the reference count to be decreased once.
+ ///
+ /// A new pointing to the same underlying object, if it
+ /// has not yet been disposed; otherwise, if this reference to the underlying object
+ /// has already been disposed.
+ public ReferenceCountedDisposable? TryAddReference()
+ => TryAddReferenceImpl(_instance, _boxedReferenceCount);
+
+ ///
+ /// Provides the implementation for and
+ /// .
+ ///
+ private static ReferenceCountedDisposable? TryAddReferenceImpl(T? target, StrongBox referenceCount)
+ {
+ lock (referenceCount)
+ {
+ if (referenceCount.Value == 0)
+ {
+ // The target is already disposed, and cannot be reused
+ return null;
+ }
+
+ if (target == null)
+ {
+ // The current reference has been disposed, so even though it isn't disposed yet we don't have a
+ // reference to the target
+ return null;
+ }
+
+ checked
+ {
+ referenceCount.Value++;
+ }
+
+ // Must return a new instance, in order for the Dispose operation on each individual instance to
+ // be idempotent.
+ return new ReferenceCountedDisposable(target, referenceCount);
+ }
+ }
+
+ ///
+ /// Releases the current reference, causing the underlying object to be disposed if this was the last
+ /// reference.
+ ///
+ ///
+ /// After this instance is disposed, the method can no longer be used to
+ /// object a new reference to the target, even if other references to the target object are still in
+ /// use.
+ ///
+ public void Dispose()
+ {
+ T? instanceToDispose = null;
+ lock (_boxedReferenceCount)
+ {
+ if (_instance == null)
+ {
+ // Already disposed; allow multiple without error.
+ return;
+ }
+
+ _boxedReferenceCount.Value--;
+ if (_boxedReferenceCount.Value == 0)
+ {
+ instanceToDispose = _instance;
+ }
+
+ // Ensure multiple calls to Dispose for this instance are a NOP.
+ _instance = null;
+ }
+
+ instanceToDispose?.Dispose();
+ }
+
+ ///
+ /// Represents a weak reference to a which is capable of
+ /// obtaining a new counted reference up until the point when the object is no longer accessible.
+ ///
+ public struct WeakReference
+ {
+ ///
+ /// DO NOT DISPOSE OF THE TARGET.
+ ///
+ private readonly WeakReference? _weakInstance;
+ private readonly StrongBox? _boxedReferenceCount;
+
+ public WeakReference(ReferenceCountedDisposable reference)
+ : this()
+ {
+ if (reference == null)
+ {
+ throw new ArgumentNullException(nameof(reference));
+ }
+
+ T? instance = reference._instance;
+ var referenceCount = reference._boxedReferenceCount;
+ if (instance == null)
+ {
+ // The specified reference is already not valid. This case is supported by WeakReference (not
+ // unlike `new System.WeakReference(null)`), but we return early to avoid an unnecessary
+ // allocation in this case.
+ return;
+ }
+
+ _weakInstance = new WeakReference(instance);
+ _boxedReferenceCount = referenceCount;
+ }
+
+ ///
+ /// Increments the reference count for the disposable object, and returns a new disposable reference to
+ /// it.
+ ///
+ ///
+ /// Unlike , this method is capable of
+ /// adding a reference to the underlying instance all the way up to the point where it is finally
+ /// disposed.
+ ///
+ /// The returned object is an independent reference to the same underlying object. Disposing of
+ /// the returned value multiple times will only cause the reference count to be decreased once.
+ ///
+ /// A new pointing to the same underlying object,
+ /// if it has not yet been disposed; otherwise, if the underlying object has
+ /// already been disposed.
+ public ReferenceCountedDisposable? TryAddReference()
+ {
+ WeakReference? weakInstance = _weakInstance;
+ if (weakInstance == null || !weakInstance.TryGetTarget(out var target))
+ {
+ return null;
+ }
+
+ StrongBox? referenceCount = _boxedReferenceCount;
+ if (referenceCount == null)
+ {
+ return null;
+ }
+
+ return TryAddReferenceImpl(target, referenceCount);
+ }
+ }
+ }
+}
\ No newline at end of file