Add IDeviceEventSimulator and implement some of the Os namespace

This commit is contained in:
Alex Barney 2021-01-27 23:50:26 -07:00
parent 981b902606
commit b315e14da0
49 changed files with 1325 additions and 140 deletions

View File

@ -160,6 +160,8 @@ Module,DescriptionStart,DescriptionEnd,Flags,Namespace,Name,Summary
2,2500,2999,,,GameCardAccessFailed,
2,2503,,,,InvalidBufferForGameCard,
2,2520,,,,GameCardNotInserted,
2,2531,,,,GameCardCardAccessTimeout,
2,2951,,,,GameCardNotInsertedOnGetHandle,
2,2952,,,,InvalidGameCardHandleOnRead,
2,2954,,,,InvalidGameCardHandleOnGetCardInfo,
@ -517,6 +519,7 @@ Module,DescriptionStart,DescriptionEnd,Flags,Namespace,Name,Summary
2,4771,4779,,,SignedSystemPartitionDataCorrupted,
2,4781,,,,GameCardLogoDataCorrupted,
2,4785,,,,SimulatedDeviceDataCorrupted,
2,4790,4799,,,MultiCommitContextCorrupted,
2,4791,,,,InvalidMultiCommitContextVersion,The version of the multi-commit context file is too high for the current MultiCommitManager implementation.
@ -863,10 +866,11 @@ Module,DescriptionStart,DescriptionEnd,Flags,Namespace,Name,Summary
10,811,819,a,,RequestDeferred,
10,812,,,,RequestDeferredByUser,
11,1,,,,NotSupported,
11,100,299,a,,OutOfResource,
11,102,,,,OutOfSessionMemory,
11,131,139,,,OutOfSessions,
11,141,,,,PointerBufferTooSmall,
11,141,,,,InsufficientPointerTransferBuffer,
11,200,,,,OutOfDomains,
11,301,,,,SessionClosed,

1 Module DescriptionStart DescriptionEnd Flags Namespace Name Summary
160 2 3212 3211 AllocationMemoryFailedInFileSystemAccessorB AllocationMemoryFailedInFileSystemAccessorA
161 2 3213 3212 AllocationMemoryFailedInApplicationA AllocationMemoryFailedInFileSystemAccessorB
162 2 3215 3213 AllocationMemoryFailedInBisA AllocationMemoryFailedInApplicationA
163 2 3215 AllocationMemoryFailedInBisA
164 2 3216 AllocationMemoryFailedInBisB
165 2 3216 3217 AllocationMemoryFailedInBisB AllocationMemoryFailedInBisC
166 2 3217 3218 AllocationMemoryFailedInBisC AllocationMemoryFailedInCodeA
167 2 3218 3219 AllocationMemoryFailedInCodeA AllocationMemoryFailedInContentA
519 2 6308 UnsupportedOperateRangeForSwitchStorage
520 2 6309 UnsupportedOperateRangeForStorageServiceObjectAdapter
521 2 6310 UnsupportedWriteForAesCtrCounterExtendedStorage
522 2 6311 UnsupportedSetSizeForAesCtrCounterExtendedStorage
523 2 6311 6312 UnsupportedSetSizeForAesCtrCounterExtendedStorage UnsupportedOperateRangeForAesCtrCounterExtendedStorage
524 2 6312 6313 UnsupportedOperateRangeForAesCtrCounterExtendedStorage UnsupportedWriteForAesCtrStorageExternal
525 2 6313 6314 UnsupportedWriteForAesCtrStorageExternal UnsupportedSetSizeForAesCtrStorageExternal
866 24 74 AbortTransactionSoftwareTimeout
867 24 75 CommandInhibitCmdSoftwareTimeout
868 24 76 CommandInhibitDatSoftwareTimeout
869 24 77 BusySoftwareTimeout
870 24 77 78 BusySoftwareTimeout IssueTuningCommandSoftwareTimeout
871 24 78 79 IssueTuningCommandSoftwareTimeout TuningFailed
872 24 79 80 TuningFailed MmcInitializationSoftwareTimeout
873 24 80 81 MmcInitializationSoftwareTimeout MmcNotSupportExtendedCsd
874 24 81 82 MmcNotSupportExtendedCsd UnexpectedMmcExtendedCsdValue
875 24 82 83 UnexpectedMmcExtendedCsdValue MmcEraseSoftwareTimeout
876 24 83 84 MmcEraseSoftwareTimeout SdCardValidationError

View File

@ -0,0 +1,95 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading;
namespace LibHac.Common
{
internal readonly ref struct InitializationGuard
{
private readonly Ref<nint> _guard;
private readonly object _mutex;
public bool IsInitialized => _mutex == null;
private const byte GuardBitComplete = 1 << 0;
[Flags]
private enum InitStatus : byte
{
Complete = 1 << 0,
Pending = 1 << 1,
Waiting = 1 << 2
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public InitializationGuard(ref nint guard, object mutex)
{
if (IsGuardInitialized(guard) || !AcquireGuard(ref guard, mutex))
{
this = default;
return;
}
_guard = new Ref<nint>(ref guard);
_mutex = mutex;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
if (!IsInitialized)
{
ReleaseGuard(ref _guard.Value, _mutex);
}
}
public static bool AcquireGuard(ref nint guard, object mutex)
{
if (SpanHelpers.AsByteSpan(ref guard)[0] == GuardBitComplete)
return false;
return AcquireInitByte(ref Unsafe.As<byte, InitStatus>(ref SpanHelpers.AsByteSpan(ref guard)[1]), mutex);
}
public static void ReleaseGuard(ref nint guard, object mutex)
{
SpanHelpers.AsByteSpan(ref guard)[0] = GuardBitComplete;
ReleaseInitByte(ref Unsafe.As<byte, InitStatus>(ref SpanHelpers.AsByteSpan(ref guard)[1]), mutex);
}
private static bool AcquireInitByte(ref InitStatus initByte, object mutex)
{
lock (mutex)
{
while (initByte.HasFlag(InitStatus.Pending))
{
initByte |= InitStatus.Waiting;
Monitor.Wait(mutex);
}
if (initByte == InitStatus.Complete)
return false;
initByte = InitStatus.Pending;
return true;
}
}
private static void ReleaseInitByte(ref InitStatus initByte, object mutex)
{
lock (mutex)
{
bool hasWaiting = initByte.HasFlag(InitStatus.Waiting);
initByte = InitStatus.Complete;
if (hasWaiting)
Monitor.PulseAll(mutex);
}
}
private static bool IsGuardInitialized(nint guard)
{
return (guard & 1) != 0;
}
}
}

View File

@ -1,14 +1,12 @@
using System;
namespace LibHac.Fs
namespace LibHac.Fs
{
public static class AccessLogHelpers
{
public static string BuildDefaultLogLine(Result result, TimeSpan startTime, TimeSpan endTime, int handleId,
public static string BuildDefaultLogLine(Result result, System.TimeSpan startTime, System.TimeSpan endTime, int handleId,
string message, string caller)
{
return
$"FS_ACCESS: {{ start: {(long) startTime.TotalMilliseconds,9}, end: {(long) endTime.TotalMilliseconds,9}, result: 0x{result.Value:x8}, handle: 0x{handleId:x8}, function: \"{caller}\"{message} }}";
$"FS_ACCESS: {{ start: {(long)startTime.TotalMilliseconds,9}, end: {(long)endTime.TotalMilliseconds,9}, result: 0x{result.Value:x8}, handle: 0x{handleId:x8}, function: \"{caller}\"{message} }}";
}
}
}

View File

@ -12,4 +12,13 @@ namespace LibHac.Fs
[FieldOffset(0x28)] public int RomFsRecoveredByInvalidateCacheCount;
[FieldOffset(0x2C)] public int SaveDataIndexCount;
}
[StructLayout(LayoutKind.Explicit, Size = 0x10)]
public struct StorageErrorInfo
{
[FieldOffset(0x00)] public int NumActivationFailures;
[FieldOffset(0x04)] public int NumActivationErrorCorrections;
[FieldOffset(0x08)] public int NumReadWriteFailures;
[FieldOffset(0x0C)] public int NumReadWriteErrorCorrections;
}
}

View File

@ -136,22 +136,22 @@ namespace LibHac.Fs
return handle.Directory.Parent.IsAccessLogEnabled;
}
internal void OutputAccessLog(Result result, TimeSpan startTime, TimeSpan endTime, string message, [CallerMemberName] string caller = "")
internal void OutputAccessLog(Result result, System.TimeSpan startTime, System.TimeSpan endTime, string message, [CallerMemberName] string caller = "")
{
OutputAccessLogImpl(result, startTime, endTime, 0, message, caller);
}
internal void OutputAccessLog(Result result, TimeSpan startTime, TimeSpan endTime, FileHandle handle, string message, [CallerMemberName] string caller = "")
internal void OutputAccessLog(Result result, System.TimeSpan startTime, System.TimeSpan endTime, FileHandle handle, string message, [CallerMemberName] string caller = "")
{
OutputAccessLogImpl(result, startTime, endTime, handle.GetId(), message, caller);
}
internal void OutputAccessLog(Result result, TimeSpan startTime, TimeSpan endTime, DirectoryHandle handle, string message, [CallerMemberName] string caller = "")
internal void OutputAccessLog(Result result, System.TimeSpan startTime, System.TimeSpan endTime, DirectoryHandle handle, string message, [CallerMemberName] string caller = "")
{
OutputAccessLogImpl(result, startTime, endTime, handle.GetId(), message, caller);
}
internal void OutputAccessLogUnlessResultSuccess(Result result, TimeSpan startTime, TimeSpan endTime, string message, [CallerMemberName] string caller = "")
internal void OutputAccessLogUnlessResultSuccess(Result result, System.TimeSpan startTime, System.TimeSpan endTime, string message, [CallerMemberName] string caller = "")
{
if (result.IsFailure())
{
@ -159,7 +159,7 @@ namespace LibHac.Fs
}
}
internal void OutputAccessLogUnlessResultSuccess(Result result, TimeSpan startTime, TimeSpan endTime, FileHandle handle, string message, [CallerMemberName] string caller = "")
internal void OutputAccessLogUnlessResultSuccess(Result result, System.TimeSpan startTime, System.TimeSpan endTime, FileHandle handle, string message, [CallerMemberName] string caller = "")
{
if (result.IsFailure())
{
@ -167,7 +167,7 @@ namespace LibHac.Fs
}
}
internal void OutputAccessLogUnlessResultSuccess(Result result, TimeSpan startTime, TimeSpan endTime, DirectoryHandle handle, string message, [CallerMemberName] string caller = "")
internal void OutputAccessLogUnlessResultSuccess(Result result, System.TimeSpan startTime, System.TimeSpan endTime, DirectoryHandle handle, string message, [CallerMemberName] string caller = "")
{
if (result.IsFailure())
{
@ -175,7 +175,7 @@ namespace LibHac.Fs
}
}
internal void OutputAccessLogImpl(Result result, TimeSpan startTime, TimeSpan endTime, int handleId,
internal void OutputAccessLogImpl(Result result, System.TimeSpan startTime, System.TimeSpan endTime, int handleId,
string message, [CallerMemberName] string caller = "")
{
if (GlobalAccessLogMode.HasFlag(GlobalAccessLogMode.Log))
@ -199,9 +199,9 @@ namespace LibHac.Fs
if (IsEnabledAccessLog(logTarget))
{
TimeSpan startTime = Time.GetCurrent();
System.TimeSpan startTime = Time.GetCurrent();
rc = operation();
TimeSpan endTime = Time.GetCurrent();
System.TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(rc, startTime, endTime, textGenerator(), caller);
}
@ -220,9 +220,9 @@ namespace LibHac.Fs
if (IsEnabledAccessLog(logTarget) && handle.File.Parent.IsAccessLogEnabled)
{
TimeSpan startTime = Time.GetCurrent();
System.TimeSpan startTime = Time.GetCurrent();
rc = operation();
TimeSpan endTime = Time.GetCurrent();
System.TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(rc, startTime, endTime, handle, textGenerator(), caller);
}
@ -241,9 +241,9 @@ namespace LibHac.Fs
if (IsEnabledAccessLog(logTarget))
{
TimeSpan startTime = Time.GetCurrent();
System.TimeSpan startTime = Time.GetCurrent();
rc = operation();
TimeSpan endTime = Time.GetCurrent();
System.TimeSpan endTime = Time.GetCurrent();
OutputAccessLogUnlessResultSuccess(rc, startTime, endTime, textGenerator(), caller);
}

View File

@ -15,9 +15,9 @@ namespace LibHac.Fs
if (IsEnabledAccessLog() && IsEnabledHandleAccessLog(handle))
{
TimeSpan startTime = Time.GetCurrent();
System.TimeSpan startTime = Time.GetCurrent();
rc = handle.Directory.Read(out entriesRead, entryBuffer);
TimeSpan endTime = Time.GetCurrent();
System.TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(rc, startTime, endTime, handle, string.Empty);
}
@ -33,9 +33,9 @@ namespace LibHac.Fs
{
if (IsEnabledAccessLog() && IsEnabledHandleAccessLog(handle))
{
TimeSpan startTime = Time.GetCurrent();
System.TimeSpan startTime = Time.GetCurrent();
handle.Directory.Dispose();
TimeSpan endTime = Time.GetCurrent();
System.TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(Result.Success, startTime, endTime, handle, string.Empty);
}

View File

@ -30,9 +30,9 @@ namespace LibHac.Fs
if (IsEnabledAccessLog() && IsEnabledHandleAccessLog(handle))
{
TimeSpan startTime = Time.GetCurrent();
System.TimeSpan startTime = Time.GetCurrent();
rc = handle.File.Read(out bytesRead, offset, destination, in option);
TimeSpan endTime = Time.GetCurrent();
System.TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(rc, startTime, endTime, handle, $", offset: {offset}, size: {destination.Length}");
}
@ -55,9 +55,9 @@ namespace LibHac.Fs
if (IsEnabledAccessLog() && IsEnabledHandleAccessLog(handle))
{
TimeSpan startTime = Time.GetCurrent();
System.TimeSpan startTime = Time.GetCurrent();
rc = handle.File.Write(offset, source, in option);
TimeSpan endTime = Time.GetCurrent();
System.TimeSpan endTime = Time.GetCurrent();
string optionString = option.HasFlushFlag() ? "" : $", write_option: {option}";

View File

@ -1,4 +1,3 @@
using System;
using LibHac.Common;
using LibHac.Fs.Accessors;
using LibHac.Fs.Fsa;
@ -14,9 +13,9 @@ namespace LibHac.Fs
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
{
TimeSpan startTime = Time.GetCurrent();
System.TimeSpan startTime = Time.GetCurrent();
rc = fileSystem.CreateDirectory(subPath);
TimeSpan endTime = Time.GetCurrent();
System.TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(rc, startTime, endTime, $", path: \"{path.ToString()}\"");
}
@ -40,9 +39,9 @@ namespace LibHac.Fs
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
{
TimeSpan startTime = Time.GetCurrent();
System.TimeSpan startTime = Time.GetCurrent();
rc = fileSystem.CreateFile(subPath, size, options);
TimeSpan endTime = Time.GetCurrent();
System.TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(rc, startTime, endTime, $", path: \"{path.ToString()}\", size: {size}");
}
@ -61,9 +60,9 @@ namespace LibHac.Fs
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
{
TimeSpan startTime = Time.GetCurrent();
System.TimeSpan startTime = Time.GetCurrent();
rc = fileSystem.DeleteDirectory(subPath);
TimeSpan endTime = Time.GetCurrent();
System.TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(rc, startTime, endTime, $", path: \"{path.ToString()}\"");
}
@ -82,9 +81,9 @@ namespace LibHac.Fs
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
{
TimeSpan startTime = Time.GetCurrent();
System.TimeSpan startTime = Time.GetCurrent();
rc = fileSystem.DeleteDirectoryRecursively(subPath);
TimeSpan endTime = Time.GetCurrent();
System.TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(rc, startTime, endTime, $", path: \"{path.ToString()}\"");
}
@ -103,9 +102,9 @@ namespace LibHac.Fs
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
{
TimeSpan startTime = Time.GetCurrent();
System.TimeSpan startTime = Time.GetCurrent();
rc = fileSystem.CleanDirectoryRecursively(subPath);
TimeSpan endTime = Time.GetCurrent();
System.TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(rc, startTime, endTime, $", path: \"{path.ToString()}\"");
}
@ -124,9 +123,9 @@ namespace LibHac.Fs
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
{
TimeSpan startTime = Time.GetCurrent();
System.TimeSpan startTime = Time.GetCurrent();
rc = fileSystem.DeleteFile(subPath);
TimeSpan endTime = Time.GetCurrent();
System.TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(rc, startTime, endTime, $", path: \"{path.ToString()}\"");
}
@ -153,9 +152,9 @@ namespace LibHac.Fs
if (IsEnabledAccessLog() && oldFileSystem.IsAccessLogEnabled)
{
TimeSpan startTime = Time.GetCurrent();
System.TimeSpan startTime = Time.GetCurrent();
rc = oldFileSystem.RenameDirectory(oldSubPath, newSubPath);
TimeSpan endTime = Time.GetCurrent();
System.TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(rc, startTime, endTime, $", path: \"{oldPath.ToString()}\", new_path: \"{newPath.ToString()}\"");
}
@ -182,9 +181,9 @@ namespace LibHac.Fs
if (IsEnabledAccessLog() && oldFileSystem.IsAccessLogEnabled)
{
TimeSpan startTime = Time.GetCurrent();
System.TimeSpan startTime = Time.GetCurrent();
rc = oldFileSystem.RenameFile(oldSubPath, newSubPath);
TimeSpan endTime = Time.GetCurrent();
System.TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(rc, startTime, endTime, $", path: \"{oldPath.ToString()}\", new_path: \"{newPath.ToString()}\"");
}
@ -205,9 +204,9 @@ namespace LibHac.Fs
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
{
TimeSpan startTime = Time.GetCurrent();
System.TimeSpan startTime = Time.GetCurrent();
rc = fileSystem.GetEntryType(out type, subPath);
TimeSpan endTime = Time.GetCurrent();
System.TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(rc, startTime, endTime, $", path: \"{path.ToString()}\"");
}
@ -228,10 +227,10 @@ namespace LibHac.Fs
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
{
TimeSpan startTime = Time.GetCurrent();
System.TimeSpan startTime = Time.GetCurrent();
rc = fileSystem.OpenFile(out FileAccessor file, subPath, mode);
handle = new FileHandle(file);
TimeSpan endTime = Time.GetCurrent();
System.TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(rc, startTime, endTime, handle, $", path: \"{path.ToString()}\", open_mode: {mode}");
}
@ -253,10 +252,10 @@ namespace LibHac.Fs
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
{
TimeSpan startTime = Time.GetCurrent();
System.TimeSpan startTime = Time.GetCurrent();
rc = fileSystem.OpenDirectory(out DirectoryAccessor dir, subPath, mode);
handle = new DirectoryHandle(dir);
TimeSpan endTime = Time.GetCurrent();
System.TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(rc, startTime, endTime, handle, $", path: \"{path.ToString()}\", open_mode: {mode}");
}
@ -306,9 +305,9 @@ namespace LibHac.Fs
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
{
TimeSpan startTime = Time.GetCurrent();
System.TimeSpan startTime = Time.GetCurrent();
rc = fileSystem.Commit();
TimeSpan endTime = Time.GetCurrent();
System.TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\"");
}

View File

@ -156,11 +156,11 @@ namespace LibHac.Fs
if (IsEnabledAccessLog() && IsEnabledFileSystemAccessorAccessLog(mountName))
{
TimeSpan startTime = Time.GetCurrent();
System.TimeSpan startTime = Time.GetCurrent();
rc = MountTable.Unmount(mountNameStr);
TimeSpan endTime = Time.GetCurrent();
System.TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(rc, startTime, endTime, $", name: \"{mountNameStr}\"");
}
else

View File

@ -199,10 +199,26 @@ namespace LibHac.Fs
}
public enum SimulatingDeviceDetectionMode
{
NoSimulation = 0,
DeviceAttached = 1,
DeviceRemoved = 2
}
public enum SimulatingDeviceAccessFailureEventType
{
None = 0,
Inserted = 1,
NotInserted = 2
AccessTimeoutFailure = 1,
AccessFailure = 2,
DataCorruption = 3
}
[Flags]
public enum SimulatingDeviceTargetOperation
{
None = 0,
Read = 1 << 0,
Write = 1 << 1
}
public enum FsStackUsageThreadType
@ -211,4 +227,34 @@ namespace LibHac.Fs
IpcWorker = 1,
PipelineWorker = 2
}
public enum MmcPartition
{
UserData = 0,
BootPartition1 = 1,
BootPartition2 = 2
}
public enum MmcSpeedMode
{
Identification = 0,
LegacySpeed = 1,
HighSpeed = 2,
Hs200 = 3,
Hs400 = 4,
Unknown = 5
}
public enum SdCardSpeedMode
{
Identification = 0,
DefaultSpeed = 1,
HighSpeed = 2,
Sdr12 = 3,
Sdr25 = 4,
Sdr50 = 5,
Sdr104 = 6,
Ddr50 = 7,
Unknown = 8,
}
}

View File

@ -1,10 +1,9 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.CompilerServices;
namespace LibHac.Fs
{
public interface IAccessLog
{
void Log(Result result, TimeSpan startTime, TimeSpan endTime, int handleId, string message, [CallerMemberName] string caller = "");
void Log(Result result, System.TimeSpan startTime, System.TimeSpan endTime, int handleId, string message, [CallerMemberName] string caller = "");
}
}

View File

@ -17,6 +17,18 @@ namespace LibHac.Fs.Impl
{
BaseStorage = baseStorage.AddReference();
}
protected StorageServiceObjectAdapter(ref ReferenceCountedDisposable<IStorageSf> baseStorage)
{
BaseStorage = Shared.Move(ref baseStorage);
}
public static ReferenceCountedDisposable<IStorage> CreateShared(
ref ReferenceCountedDisposable<IStorageSf> baseStorage)
{
return new ReferenceCountedDisposable<IStorage>(new StorageServiceObjectAdapter(ref baseStorage));
}
protected override Result DoRead(long offset, Span<byte> destination)
{
return BaseStorage.Target.Read(offset, destination);

View File

@ -232,6 +232,8 @@ namespace LibHac.Fs
public static Result.Base InvalidBufferForGameCard => new Result.Base(ModuleFs, 2503);
/// <summary>Error code: 2002-2520; Inner value: 0x13b002</summary>
public static Result.Base GameCardNotInserted => new Result.Base(ModuleFs, 2520);
/// <summary>Error code: 2002-2531; Inner value: 0x13c602</summary>
public static Result.Base GameCardCardAccessTimeout => new Result.Base(ModuleFs, 2531);
/// <summary>Error code: 2002-2951; Inner value: 0x170e02</summary>
public static Result.Base GameCardNotInsertedOnGetHandle => new Result.Base(ModuleFs, 2951);
/// <summary>Error code: 2002-2952; Inner value: 0x171002</summary>
@ -907,6 +909,8 @@ namespace LibHac.Fs
/// <summary>Error code: 2002-4781; Inner value: 0x255a02</summary>
public static Result.Base GameCardLogoDataCorrupted => new Result.Base(ModuleFs, 4781);
/// <summary>Error code: 2002-4785; Inner value: 0x256202</summary>
public static Result.Base SimulatedDeviceDataCorrupted => new Result.Base(ModuleFs, 4785);
/// <summary>Error code: 2002-4790; Range: 4790-4799; Inner value: 0x256c02</summary>
public static Result.Base MultiCommitContextCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4790, 4799); }

View File

@ -8,7 +8,7 @@ namespace LibHac.Fs
/// </summary>
public class SdCardAccessLog : IAccessLog
{
public void Log(Result result, TimeSpan startTime, TimeSpan endTime, int handleId, string message, string caller = "")
public void Log(Result result, System.TimeSpan startTime, System.TimeSpan endTime, int handleId, string message, string caller = "")
{
throw new NotImplementedException();
}

View File

@ -1,5 +1,4 @@
using System;
using LibHac.Common;
using LibHac.Common;
using LibHac.Fs.Impl;
using LibHac.FsSrv.Sf;
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
@ -14,9 +13,9 @@ namespace LibHac.Fs.Shim
if (fs.IsEnabledAccessLog(AccessLogTarget.System))
{
TimeSpan startTime = fs.Time.GetCurrent();
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = Run(fs, mountName, path);
TimeSpan endTime = fs.Time.GetCurrent();
System.TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLog(rc, startTime, endTime, "");
}

View File

@ -1,5 +1,4 @@
using System;
using LibHac.Common;
using LibHac.Common;
using LibHac.Fs.Impl;
using LibHac.FsSrv.Sf;
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
@ -14,9 +13,9 @@ namespace LibHac.Fs.Shim
if (fs.IsEnabledAccessLog(AccessLogTarget.System))
{
TimeSpan startTime = fs.Time.GetCurrent();
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = MountBcatSaveDataImpl(fs, mountName, applicationId);
TimeSpan endTime = fs.Time.GetCurrent();
System.TimeSpan endTime = fs.Time.GetCurrent();
string logMessage = $", name: \"{mountName.ToString()}\", applicationid: 0x{applicationId}\"";

View File

@ -59,9 +59,9 @@ namespace LibHac.Fs.Shim
if (fs.IsEnabledAccessLog(AccessLogTarget.System))
{
TimeSpan startTime = fs.Time.GetCurrent();
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = MountBisImpl(fs, mountName, partitionId, rootPath);
TimeSpan endTime = fs.Time.GetCurrent();
System.TimeSpan endTime = fs.Time.GetCurrent();
string logMessage = $", name: \"{mountName.ToString()}\", bispartitionid: {partitionId}, path: \"{rootPath.ToString()}\"";

View File

@ -1,5 +1,4 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs.Impl;
using LibHac.FsSrv.Sf;
@ -17,9 +16,9 @@ namespace LibHac.Fs.Shim
if (fs.IsEnabledAccessLog(AccessLogTarget.System))
{
TimeSpan startTime = fs.Time.GetCurrent();
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = MountCodeImpl(fs, out verificationData, mountName, path, programId);
TimeSpan endTime = fs.Time.GetCurrent();
System.TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLog(rc, startTime, endTime,
$", name: \"{mountName.ToString()}\", name: \"{path.ToString()}\", programid: 0x{programId}");

View File

@ -205,9 +205,9 @@ namespace LibHac.Fs.Shim
logMessage = $", name: \"{mountName.ToString()}\"";
}
TimeSpan startTime = fs.Time.GetCurrent();
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = PreMountHost(out nameGenerator, mountName, path);
TimeSpan endTime = fs.Time.GetCurrent();
System.TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLogUnlessResultSuccess(rc, startTime, endTime, logMessage, caller);
}
@ -222,9 +222,9 @@ namespace LibHac.Fs.Shim
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
{
TimeSpan startTime = fs.Time.GetCurrent();
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = OpenHostFileSystem(fs, out hostFileSystem, mountName, path, option);
TimeSpan endTime = fs.Time.GetCurrent();
System.TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLogUnlessResultSuccess(rc, startTime, endTime, logMessage, caller);
}
@ -237,9 +237,9 @@ namespace LibHac.Fs.Shim
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
{
TimeSpan startTime = fs.Time.GetCurrent();
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = fs.Register(mountName, hostFileSystem, nameGenerator);
TimeSpan endTime = fs.Time.GetCurrent();
System.TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLog(rc, startTime, endTime, logMessage, caller);
}

View File

@ -1,5 +1,4 @@
using System;
using LibHac.Common;
using LibHac.Common;
using LibHac.Fs.Impl;
using LibHac.FsSrv.Sf;
using LibHac.Ncm;
@ -15,9 +14,9 @@ namespace LibHac.Fs.Shim
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
{
TimeSpan startTime = fs.Time.GetCurrent();
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, userId, SaveDataType.Account, false, 0);
TimeSpan endTime = fs.Time.GetCurrent();
System.TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\", applicationid: 0x{applicationId}, userid: 0x{userId}");
}
@ -40,9 +39,9 @@ namespace LibHac.Fs.Shim
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
{
TimeSpan startTime = fs.Time.GetCurrent();
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, userId, SaveDataType.Account, true, 0);
TimeSpan endTime = fs.Time.GetCurrent();
System.TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\", applicationid: 0x{applicationId}, userid: 0x{userId}");
}
@ -65,9 +64,9 @@ namespace LibHac.Fs.Shim
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
{
TimeSpan startTime = fs.Time.GetCurrent();
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.Temporary, default, default, SaveDataType.Temporary, false, 0);
TimeSpan endTime = fs.Time.GetCurrent();
System.TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\"");
}
@ -90,9 +89,9 @@ namespace LibHac.Fs.Shim
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
{
TimeSpan startTime = fs.Time.GetCurrent();
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, default, default, SaveDataType.Cache, false, 0);
TimeSpan endTime = fs.Time.GetCurrent();
System.TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\"");
}
@ -115,9 +114,9 @@ namespace LibHac.Fs.Shim
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
{
TimeSpan startTime = fs.Time.GetCurrent();
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, default, default, SaveDataType.Cache, false, (ushort)index);
TimeSpan endTime = fs.Time.GetCurrent();
System.TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\", index: {index}");
}
@ -140,9 +139,9 @@ namespace LibHac.Fs.Shim
if (fs.IsEnabledAccessLog(AccessLogTarget.System))
{
TimeSpan startTime = fs.Time.GetCurrent();
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, default, SaveDataType.Cache, false, 0);
TimeSpan endTime = fs.Time.GetCurrent();
System.TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\", applicationid: 0x{applicationId}");
}
@ -165,9 +164,9 @@ namespace LibHac.Fs.Shim
if (fs.IsEnabledAccessLog(AccessLogTarget.System))
{
TimeSpan startTime = fs.Time.GetCurrent();
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, default, SaveDataType.Cache, false, (ushort)index);
TimeSpan endTime = fs.Time.GetCurrent();
System.TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\", applicationid: 0x{applicationId}, index: {index}");
}

View File

@ -421,9 +421,9 @@ namespace LibHac.Fs.Shim
if (FsClient.IsEnabledAccessLog(AccessLogTarget.System))
{
TimeSpan startTime = FsClient.Time.GetCurrent();
System.TimeSpan startTime = FsClient.Time.GetCurrent();
rc = Reader.Target.Read(out readCount, byteBuffer);
TimeSpan endTime = FsClient.Time.GetCurrent();
System.TimeSpan endTime = FsClient.Time.GetCurrent();
FsClient.OutputAccessLog(rc, startTime, endTime, $", size: {buffer.Length}");
}

View File

@ -1,5 +1,4 @@
using System;
using LibHac.Common;
using LibHac.Common;
using LibHac.Fs.Impl;
using LibHac.FsSrv.Sf;
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
@ -14,9 +13,9 @@ namespace LibHac.Fs.Shim
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
{
TimeSpan startTime = fs.Time.GetCurrent();
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = Run(fs, mountName);
TimeSpan endTime = fs.Time.GetCurrent();
System.TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLog(rc, startTime, endTime, "");
}

View File

@ -24,6 +24,11 @@ namespace LibHac.FsSrv
public bool IsDebugMode { get; }
private ITimeSpanGenerator Timer { get; }
// Functions in the nn::fssrv::detail namespace use this field.
// Possibly move this to the main class if the separation doesn't seem necessary.
internal FileSystemServerImpl Impl;
internal ref FileSystemServerGlobals Globals => ref Impl.Globals;
/// <summary>
/// Creates a new <see cref="FileSystemServer"/> and registers its services using the provided HOS client.
/// </summary>
@ -37,6 +42,10 @@ namespace LibHac.FsSrv
if (config.DeviceOperator == null)
throw new ArgumentException("DeviceOperator must not be null");
Impl = new FileSystemServerImpl();
Impl.Globals.Hos = horizonClient;
Impl.Globals.InitMutex = new object();
Hos = horizonClient;
IsDebugMode = false;
@ -258,4 +267,18 @@ namespace LibHac.FsSrv
/// </summary>
public ITimeSpanGenerator TimeSpanGenerator { get; set; }
}
// Functions in the nn::fssrv::detail namespace use this struct.
// Possibly move this to the main class if the separation doesn't seem necessary.
internal struct FileSystemServerImpl
{
public FileSystemServerGlobals Globals;
}
internal struct FileSystemServerGlobals
{
public HorizonClient Hos;
public object InitMutex;
public DeviceEventSimulatorGlobals DeviceEventSimulator;
}
}

View File

@ -0,0 +1,186 @@
using LibHac.Common;
using LibHac.Fs;
using LibHac.Os;
namespace LibHac.FsSrv.Impl
{
internal struct DeviceEventSimulatorGlobals
{
public GameCardEventSimulator GameCardEventSimulator;
public SdCardEventSimulator SdCardEventSimulator;
public nint GameCardEventSimulatorInit;
public nint SdCardEventSimulatorInit;
}
internal static class DeviceEventSimulatorGlobalMethods
{
public static SdCardEventSimulator GetSdCardEventSimulator(this ref FileSystemServerImpl fs)
{
ref DeviceEventSimulatorGlobals g = ref fs.Globals.DeviceEventSimulator;
using var guard = new InitializationGuard(ref g.SdCardEventSimulatorInit, fs.Globals.InitMutex);
if (guard.IsInitialized)
return g.SdCardEventSimulator;
g.SdCardEventSimulator = new SdCardEventSimulator(fs.Globals.Hos.Os);
return g.SdCardEventSimulator;
}
public static GameCardEventSimulator GetGameCardEventSimulator(this ref FileSystemServerImpl fs)
{
ref DeviceEventSimulatorGlobals g = ref fs.Globals.DeviceEventSimulator;
using var guard = new InitializationGuard(ref g.GameCardEventSimulatorInit, fs.Globals.InitMutex);
if (guard.IsInitialized)
return g.GameCardEventSimulator;
g.GameCardEventSimulator = new GameCardEventSimulator(fs.Globals.Hos.Os);
return g.GameCardEventSimulator;
}
}
// ReSharper disable once InconsistentNaming
public abstract class IDeviceEventSimulator
{
private bool _isEventSet;
private bool _isDetectionSimulationEnabled;
private SdkRecursiveMutex _mutex;
private SimulatingDeviceDetectionMode _detectionSimulationMode;
private SimulatingDeviceAccessFailureEventType _simulatedFailureType;
private SimulatingDeviceTargetOperation _simulatedOperation;
private Result _failureResult;
private bool _isRecurringEvent;
private int _timeoutLengthMs;
private OsState _os;
public IDeviceEventSimulator(OsState os, int timeoutMs)
{
_os = os;
_timeoutLengthMs = timeoutMs;
_mutex = new SdkRecursiveMutex();
}
public virtual Result GetCorrespondingResult(SimulatingDeviceAccessFailureEventType eventType)
{
return Result.Success;
}
public void SetDeviceEvent(SimulatingDeviceTargetOperation operation,
SimulatingDeviceAccessFailureEventType failureType, Result failureResult, bool isRecurringEvent)
{
using ScopedLock<SdkRecursiveMutex> lk = ScopedLock.Lock(ref _mutex);
if (failureResult.IsFailure())
_failureResult = failureResult;
_isEventSet = true;
_simulatedFailureType = failureType;
_simulatedOperation = operation;
_isRecurringEvent = isRecurringEvent;
}
public void ClearDeviceEvent()
{
using ScopedLock<SdkRecursiveMutex> lk = ScopedLock.Lock(ref _mutex);
_isEventSet = false;
_simulatedFailureType = SimulatingDeviceAccessFailureEventType.None;
_simulatedOperation = SimulatingDeviceTargetOperation.None;
_failureResult = Result.Success;
_isRecurringEvent = false;
}
public void SetDetectionSimulationMode(SimulatingDeviceDetectionMode mode)
{
using ScopedLock<SdkRecursiveMutex> lk = ScopedLock.Lock(ref _mutex);
_isDetectionSimulationEnabled = mode != SimulatingDeviceDetectionMode.NoSimulation;
_detectionSimulationMode = mode;
}
public void ClearDetectionSimulationMode()
{
SetDetectionSimulationMode(SimulatingDeviceDetectionMode.NoSimulation);
}
public Result CheckSimulatedAccessFailureEvent(SimulatingDeviceTargetOperation operation)
{
if (_isEventSet)
return Result.Success;
using ScopedLock<SdkRecursiveMutex> lk = ScopedLock.Lock(ref _mutex);
if ((_simulatedOperation & operation) == 0)
return Result.Success;
Result result = GetCorrespondingResult(_simulatedFailureType);
if (result.IsFailure() && _failureResult.IsFailure())
result = _failureResult;
if (_simulatedFailureType == SimulatingDeviceAccessFailureEventType.AccessTimeoutFailure)
SimulateTimeout();
if (!_isRecurringEvent)
ClearDeviceEvent();
return result;
}
public bool FilterDetectionState(bool actualState)
{
if (!_isDetectionSimulationEnabled)
return actualState;
bool simulatedState = _detectionSimulationMode switch
{
SimulatingDeviceDetectionMode.NoSimulation => actualState,
SimulatingDeviceDetectionMode.DeviceAttached => true,
SimulatingDeviceDetectionMode.DeviceRemoved => false,
_ => actualState
};
return simulatedState;
}
protected virtual void SimulateTimeout()
{
_os.SleepThread(TimeSpan.FromMilliSeconds(_timeoutLengthMs));
}
}
public class GameCardEventSimulator : IDeviceEventSimulator
{
public GameCardEventSimulator(OsState os) : base(os, 2000) { }
public override Result GetCorrespondingResult(SimulatingDeviceAccessFailureEventType eventType)
{
return eventType switch
{
SimulatingDeviceAccessFailureEventType.None => Result.Success,
SimulatingDeviceAccessFailureEventType.AccessTimeoutFailure => ResultFs.GameCardCardAccessTimeout.Log(),
SimulatingDeviceAccessFailureEventType.AccessFailure => ResultFs.GameCardAccessFailed.Log(),
SimulatingDeviceAccessFailureEventType.DataCorruption => ResultFs.SimulatedDeviceDataCorrupted.Log(),
_ => ResultFs.InvalidArgument.Log()
};
}
}
public class SdCardEventSimulator : IDeviceEventSimulator
{
public SdCardEventSimulator(OsState os) : base(os, 2000) { }
public override Result GetCorrespondingResult(SimulatingDeviceAccessFailureEventType eventType)
{
return eventType switch
{
SimulatingDeviceAccessFailureEventType.None => Result.Success,
SimulatingDeviceAccessFailureEventType.AccessTimeoutFailure => ResultFs.PortSdCardResponseTimeoutError.Log(),
SimulatingDeviceAccessFailureEventType.AccessFailure => ResultFs.SdCardAccessFailed.Log(),
SimulatingDeviceAccessFailureEventType.DataCorruption => ResultFs.SimulatedDeviceDataCorrupted.Log(),
_ => ResultFs.InvalidArgument.Log()
};
}
}
}

View File

@ -14,7 +14,7 @@ namespace LibHac.FsSystem
public bool TryLock()
{
return _semaphore.Wait(TimeSpan.Zero);
return _semaphore.Wait(System.TimeSpan.Zero);
}
public void Unlock()

View File

@ -1,4 +1,5 @@
using System.Threading;
using System.Diagnostics;
using System.Threading;
using LibHac.Bcat;
using LibHac.Common;
using LibHac.Diag;
@ -14,20 +15,24 @@ namespace LibHac
public class Horizon
{
private const int InitialProcessCountMax = 0x50;
internal long StartTick { get; }
internal ITimeSpanGenerator Time { get; }
internal ServiceManager ServiceManager { get; }
private HorizonClient LoaderClient { get; }
// long instead of ulong because the ulong Interlocked.Increment overload
// wasn't added until .NET 5
private long _currentInitialProcessId;
private long _currentProcessId;
private ulong _currentInitialProcessId;
private ulong _currentProcessId;
// Todo: Initialize with a configuration object
public Horizon(ITimeSpanGenerator timer, FileSystemServerConfig fsServerConfig)
{
_currentProcessId = InitialProcessCountMax;
Time = timer ?? new StopWatchTimeSpanGenerator();
StartTick = Stopwatch.GetTimestamp();
ServiceManager = new ServiceManager();
// ReSharper disable ObjectCreationAsStatement
@ -40,7 +45,7 @@ namespace LibHac
public HorizonClient CreatePrivilegedHorizonClient()
{
ulong processId = (ulong)Interlocked.Increment(ref _currentInitialProcessId);
ulong processId = Interlocked.Increment(ref _currentInitialProcessId);
Abort.DoAbortUnless(processId <= InitialProcessCountMax, "Created too many privileged clients.");
@ -51,7 +56,7 @@ namespace LibHac
public HorizonClient CreateHorizonClient()
{
ulong processId = (ulong)Interlocked.Increment(ref _currentProcessId);
ulong processId = Interlocked.Increment(ref _currentProcessId);
// Todo: Register process with FS

View File

@ -17,7 +17,7 @@ namespace LibHac
public FileSystemClient Fs { get; }
public ServiceManagerClient Sm { get; }
public OsClient Os { get; }
public OsState Os { get; }
public LrClient Lr { get; }
public ArpClient Arp => ArpLazy.Value;
@ -30,7 +30,7 @@ namespace LibHac
Fs = new FileSystemClient(this);
Sm = new ServiceManagerClient(horizon.ServiceManager);
Os = new OsClient(this);
Os = new OsState(this, horizon.StartTick);
Lr = new LrClient(this);
ArpLazy = new Lazy<ArpClient>(InitArpClient, true);

View File

@ -1,9 +1,7 @@
using System;
namespace LibHac
namespace LibHac
{
public interface ITimeSpanGenerator
{
TimeSpan GetCurrent();
System.TimeSpan GetCurrent();
}
}

View File

@ -0,0 +1,8 @@
namespace LibHac.Os
{
public enum ConditionVariableStatus
{
TimedOut = 0,
Success = 1
}
}

View File

@ -0,0 +1,13 @@
namespace LibHac.Os
{
public interface IBasicLockable
{
void Lock();
void Unlock();
}
public interface ILockable : IBasicLockable
{
bool TryLock();
}
}

View File

@ -0,0 +1,77 @@
using System.Threading;
using LibHac.Diag;
namespace LibHac.Os.Impl
{
internal struct InternalConditionVariableImpl
{
private object _obj;
public InternalConditionVariableImpl(nint _ = 0) => _obj = new object();
public void Initialize() => _obj = new object();
public void Signal()
{
Assert.False(Monitor.IsEntered(_obj));
Monitor.Enter(_obj);
Monitor.Pulse(_obj);
Monitor.Exit(_obj);
}
public void Broadcast()
{
Assert.False(Monitor.IsEntered(_obj));
Monitor.Enter(_obj);
Monitor.PulseAll(_obj);
Monitor.Exit(_obj);
}
public void Wait(ref InternalCriticalSection cs)
{
Assert.False(Monitor.IsEntered(_obj));
Abort.DoAbortUnless(cs.IsLockedByCurrentThread());
// Monitor.Wait doesn't allow specifying a separate mutex object. Workaround this by manually
// unlocking and locking the separate mutex object. Due to this, the order the waiting threads
// will resume is not guaranteed, and 5 Monitor calls are required instead of 1.
cs.Leave();
Monitor.Enter(_obj);
Monitor.Wait(_obj);
Monitor.Exit(_obj);
cs.Enter();
}
public ConditionVariableStatus TimedWait(ref InternalCriticalSection cs, in TimeoutHelper timeoutHelper)
{
Assert.False(Monitor.IsEntered(_obj));
Abort.DoAbortUnless(cs.IsLockedByCurrentThread());
TimeSpan remainingTime = timeoutHelper.GetTimeLeftOnTarget();
if (remainingTime <= new TimeSpan(0))
return ConditionVariableStatus.TimedOut;
// Casting to an int won't lose any data because the .NET implementation of
// GetTimeLeftOnTarget always returns a value that fits in an int.
int remainingTimeMs = (int)remainingTime.GetMilliSeconds();
cs.Leave();
Monitor.Enter(_obj);
bool acquiredBeforeTimeout = Monitor.Wait(_obj, remainingTimeMs);
Monitor.Exit(_obj);
cs.Enter();
// Short code path if we timed out even before waiting on the mutex.
if (!acquiredBeforeTimeout)
return ConditionVariableStatus.TimedOut;
// We may have timed out waiting to Enter the mutex. Check the time left again.
if (timeoutHelper.GetTimeLeftOnTarget() <= new TimeSpan(0))
return ConditionVariableStatus.TimedOut;
return ConditionVariableStatus.Success;
}
}
}

View File

@ -0,0 +1,20 @@
namespace LibHac.Os.Impl
{
internal struct InternalConditionVariable
{
private InternalConditionVariableImpl _impl;
public InternalConditionVariable(nint _ = 0)
{
_impl = new InternalConditionVariableImpl();
}
public void Initialize() => _impl.Initialize();
public void Signal() => _impl.Signal();
public void Broadcast() => _impl.Broadcast();
public void Wait(ref InternalCriticalSection cs) => _impl.Wait(ref cs);
public void TimedWait(ref InternalCriticalSection cs, in TimeoutHelper timeoutHelper) =>
_impl.TimedWait(ref cs, in timeoutHelper);
}
}

View File

@ -0,0 +1,36 @@
using System.Threading;
namespace LibHac.Os.Impl
{
internal struct InternalCriticalSectionImpl
{
private object _obj;
public void Initialize()
{
_obj = new object();
}
public void FinalizeObject() { }
public void Enter()
{
Monitor.Enter(_obj);
}
public bool TryEnter()
{
return Monitor.TryEnter(_obj);
}
public void Leave()
{
Monitor.Exit(_obj);
}
public bool IsLockedByCurrentThread()
{
return Monitor.IsEntered(_obj);
}
}
}

View File

@ -0,0 +1,20 @@
namespace LibHac.Os.Impl
{
public struct InternalCriticalSection : ILockable
{
private InternalCriticalSectionImpl _impl;
public void Initialize() => _impl.Initialize();
public void FinalizeObject() => _impl.FinalizeObject();
public void Enter() => _impl.Enter();
public bool TryEnter() => _impl.TryEnter();
public void Leave() => _impl.Leave();
public void Lock() => _impl.Enter();
public bool TryLock() => _impl.TryEnter();
public void Unlock() => _impl.Leave();
public bool IsLockedByCurrentThread() => _impl.IsLockedByCurrentThread();
}
}

View File

@ -0,0 +1,28 @@
using System;
namespace LibHac.Os.Impl
{
internal class OsResourceManager : IDisposable
{
public TickManager TickManager { get; }
// Todo: Use configuration object if/when more options are added
public OsResourceManager(long startTick)
{
TickManager = new TickManager(startTick);
}
public void Dispose()
{
TickManager.Dispose();
}
}
internal static class OsResourceManagerApi
{
public static OsResourceManager GetOsResourceManager(this OsState os)
{
return os.ResourceManager;
}
}
}

View File

@ -0,0 +1,59 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace LibHac.Os.Impl
{
internal struct TickManagerImpl : IDisposable
{
private long _tickFrequency;
private long _startTick;
private TimeSpan _maxTimeSpan;
private long _maxTick;
public TickManagerImpl(long startTick)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
TimeBeginPeriod(1);
}
_tickFrequency = Stopwatch.Frequency;
_startTick = startTick;
long nanoSecondsPerSecond = TimeSpan.FromSeconds(1).GetNanoSeconds();
if (_tickFrequency <= nanoSecondsPerSecond)
{
_maxTick = _tickFrequency * (long.MaxValue / nanoSecondsPerSecond);
_maxTimeSpan = TimeSpan.FromNanoSeconds(long.MaxValue);
}
else
{
_maxTick = long.MaxValue;
_maxTimeSpan = TimeSpan.FromSeconds(long.MaxValue / _tickFrequency);
}
}
public void Dispose()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
TimeEndPeriod(1);
}
}
public Tick GetTick() => new Tick(Stopwatch.GetTimestamp() - _startTick);
public Tick GetSystemTickOrdered() => new Tick(Stopwatch.GetTimestamp() - _startTick);
public long GetTickFrequency() => _tickFrequency;
public long GetMaxTick() => _maxTick;
public long GetMaxTimeSpanNs() => _maxTimeSpan.GetNanoSeconds();
[DllImport("winmm.dll", EntryPoint = "timeBeginPeriod")]
private static extern uint TimeBeginPeriod(uint milliseconds);
[DllImport("winmm.dll", EntryPoint = "timeEndPeriod")]
private static extern uint TimeEndPeriod(uint milliseconds);
}
}

View File

@ -0,0 +1,118 @@
using System;
using LibHac.Diag;
namespace LibHac.Os.Impl
{
internal static class TickManagerApi
{
public static TickManager GetTickManager(this OsState os) => os.GetOsResourceManager().TickManager;
public static Tick GetCurrentTick(this OsState os) => os.GetTickManager().GetTick();
public static Tick GetCurrentTickOrdered(this OsState os) => os.GetTickManager().GetSystemTickOrdered();
}
public class TickManager : IDisposable
{
private static readonly long MaxTickFrequency = long.MaxValue / TimeSpan.FromSeconds(1).GetNanoSeconds() - 1;
private TickManagerImpl _impl;
public TickManager(long startTick) => _impl = new TickManagerImpl(startTick);
~TickManager()
{
Dispose();
}
public void Dispose()
{
_impl.Dispose();
GC.SuppressFinalize(this);
}
public Tick GetTick() => _impl.GetTick();
public Tick GetSystemTickOrdered() => _impl.GetSystemTickOrdered();
public long GetTickFrequency() => _impl.GetTickFrequency();
public long GetMaxTick() => _impl.GetMaxTick();
public long GetMaxTimeSpanNs() => _impl.GetMaxTimeSpanNs();
public TimeSpan ConvertToTimespan(Tick tick)
{
// Get the tick value.
long ticks = tick.GetInt64Value();
// Get the tick frequency.
long tickFreq = GetTickFrequency();
Assert.True(tickFreq < MaxTickFrequency);
// Clamp tick to range.
if (ticks > GetMaxTick())
{
return TimeSpan.FromNanoSeconds(long.MaxValue);
}
else if (ticks < -GetMaxTick())
{
return TimeSpan.FromNanoSeconds(long.MinValue);
}
else
{
// Convert to timespan.
long nanoSecondsPerSecond = TimeSpan.FromSeconds(1).GetNanoSeconds();
long seconds = ticks / tickFreq;
long frac = ticks % tickFreq;
TimeSpan ts = TimeSpan.FromSeconds(seconds) +
TimeSpan.FromNanoSeconds(frac * nanoSecondsPerSecond / tickFreq);
Assert.True(!(ticks > 0 && ts < default(TimeSpan) || ticks < 0 && ts > default(TimeSpan)));
return ts;
}
}
public Tick ConvertToTick(TimeSpan ts)
{
// Get the TimeSpan in nanoseconds.
long ns = ts.GetNanoSeconds();
// Clamp ns to range.
if (ns > GetMaxTimeSpanNs())
{
return new Tick(long.MaxValue);
}
else if (ns < -GetMaxTimeSpanNs())
{
return new Tick(long.MinValue);
}
else
{
// Get the tick frequency.
long tickFreq = GetTickFrequency();
Assert.True(tickFreq < MaxTickFrequency);
// Convert to tick.
long nanoSecondsPerSecond = TimeSpan.FromSeconds(1).GetNanoSeconds();
bool isNegative = ns < 0;
long seconds = ns / nanoSecondsPerSecond;
long frac = ns % nanoSecondsPerSecond;
// If negative, negate seconds/frac.
if (isNegative)
{
seconds = -seconds;
frac = -frac;
}
// Calculate the tick, and invert back to negative if needed.
long ticks = (seconds * tickFreq) +
((frac * tickFreq + nanoSecondsPerSecond - 1) / nanoSecondsPerSecond);
if (isNegative)
{
ticks = -ticks;
}
return new Tick(ticks);
}
}
}
}

View File

@ -0,0 +1,45 @@
using System;
namespace LibHac.Os.Impl
{
internal static class TimeoutHelperImpl
{
public static void Sleep(OsState os, TimeSpan time)
{
if (time == new TimeSpan(0))
return;
TickManager tickManager = os.GetTickManager();
// Attempt to avoid overflow by doing the addition unsigned
ulong currentTick = (ulong)tickManager.GetTick().GetInt64Value();
ulong timeoutTick = (ulong)tickManager.ConvertToTick(time).GetInt64Value();
ulong absoluteEndTick = currentTick + timeoutTick + 1;
var endTick = new Tick((long)Math.Min(long.MaxValue, absoluteEndTick));
Tick curTick = tickManager.GetTick();
// Sleep in a loop until the requested time has past.
while (curTick < endTick)
{
Tick remaining = endTick - curTick;
int sleepTimeMs = (int)ConvertToImplTime(os, remaining).GetMilliSeconds();
System.Threading.Thread.Sleep(sleepTimeMs);
curTick = tickManager.GetTick();
}
}
public static TimeSpan ConvertToImplTime(OsState os, Tick tick)
{
TickManager tickManager = os.GetTickManager();
TimeSpan ts = tickManager.ConvertToTimespan(tick);
// .NET allows sleeping up to int.MaxValue milliseconds at a time.
long timeMs = Math.Min(int.MaxValue, ts.GetMilliSeconds());
return TimeSpan.FromMilliSeconds(timeMs);
}
}
}

View File

@ -0,0 +1,62 @@
using System;
namespace LibHac.Os.Impl
{
internal readonly struct TimeoutHelper
{
private readonly Tick _endTick;
private readonly OsState _os;
public TimeoutHelper(OsState os, TimeSpan timeout)
{
_os = os;
if (timeout == new TimeSpan(0))
{
// If timeout is zero, don't do relative tick calculations.
_endTick = new Tick(0);
}
else
{
TickManager tickManager = os.GetTickManager();
// Attempt to avoid overflow by doing the addition unsigned
ulong currentTick = (ulong)tickManager.GetTick().GetInt64Value();
ulong timeoutTick = (ulong)tickManager.ConvertToTick(timeout).GetInt64Value();
ulong absoluteEndTick = currentTick + timeoutTick + 1;
_endTick = new Tick((long)Math.Min(long.MaxValue, absoluteEndTick));
}
}
public bool TimedOut()
{
if (_endTick.GetInt64Value() == 0)
return true;
Tick currentTick = _os.GetTickManager().GetTick();
return currentTick >= _endTick;
}
public TimeSpan GetTimeLeftOnTarget()
{
// If the end tick is zero, we're expired.
if (_endTick.GetInt64Value() == 0)
return new TimeSpan(0);
// Check if we've expired.
Tick currentTick = _os.GetTickManager().GetTick();
if (currentTick >= _endTick)
return new TimeSpan(0);
// Return the converted difference as a timespan.
return TimeoutHelperImpl.ConvertToImplTime(_os, _endTick - currentTick);
}
public static void Sleep(OsState os, TimeSpan timeout)
{
TimeoutHelperImpl.Sleep(os, timeout);
}
}
}

View File

@ -1,17 +0,0 @@
namespace LibHac.Os
{
public class OsClient
{
private HorizonClient Hos { get; }
internal OsClient(HorizonClient horizonClient)
{
Hos = horizonClient;
}
public ProcessId GetCurrentProcessId()
{
return Hos.ProcessId;
}
}
}

28
src/LibHac/Os/OsState.cs Normal file
View File

@ -0,0 +1,28 @@
using System;
using LibHac.Os.Impl;
namespace LibHac.Os
{
public class OsState : IDisposable
{
private HorizonClient Hos { get; }
internal OsResourceManager ResourceManager { get; }
// Todo: Use configuration object if/when more options are added
internal OsState(HorizonClient horizonClient, long startTick)
{
Hos = horizonClient;
ResourceManager = new OsResourceManager(startTick);
}
public ProcessId GetCurrentProcessId()
{
return Hos.ProcessId;
}
public void Dispose()
{
ResourceManager.Dispose();
}
}
}

View File

@ -0,0 +1,32 @@
using System.Runtime.CompilerServices;
using LibHac.Common;
namespace LibHac.Os
{
public static class ScopedLock
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ScopedLock<TMutex> Lock<TMutex>(ref TMutex lockable) where TMutex : IBasicLockable
{
return new ScopedLock<TMutex>(ref lockable);
}
}
public ref struct ScopedLock<TMutex> where TMutex : IBasicLockable
{
private Ref<TMutex> _mutex;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ScopedLock(ref TMutex mutex)
{
_mutex = new Ref<TMutex>(ref mutex);
mutex.Lock();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
_mutex.Value.Unlock();
}
}
}

148
src/LibHac/Os/SdkMutex.cs Normal file
View File

@ -0,0 +1,148 @@
using LibHac.Diag;
using LibHac.Os.Impl;
namespace LibHac.Os
{
public class SdkMutex : ILockable
{
private SdkMutexType _mutex;
public void Initialize()
{
_mutex.Initialize();
}
public void Lock()
{
_mutex.Lock();
}
public bool TryLock()
{
return _mutex.TryLock();
}
public void Unlock()
{
_mutex.Unlock();
}
public bool IsLockedByCurrentThread()
{
return _mutex.IsLockedByCurrentThread();
}
}
public struct SdkMutexType : ILockable
{
private InternalCriticalSection _cs;
public void Initialize()
{
_cs.Initialize();
}
public void Lock()
{
Abort.DoAbortUnless(!IsLockedByCurrentThread());
_cs.Enter();
}
public bool TryLock()
{
Abort.DoAbortUnless(!IsLockedByCurrentThread());
return _cs.TryEnter();
}
public void Unlock()
{
Abort.DoAbortUnless(!IsLockedByCurrentThread());
_cs.Leave();
}
public bool IsLockedByCurrentThread()
{
return _cs.IsLockedByCurrentThread();
}
}
public class SdkRecursiveMutex : IBasicLockable
{
private SdkRecursiveMutexType _impl;
public SdkRecursiveMutex()
{
_impl.Initialize();
}
public void Lock()
{
_impl.Lock();
}
public void Unlock()
{
_impl.Unlock();
}
public bool IsLockedByCurrentThread()
{
return _impl.IsLockedByCurrentThread();
}
}
public struct SdkRecursiveMutexType : ILockable
{
private InternalCriticalSection _cs;
private int _recursiveCount;
public void Initialize()
{
_cs.Initialize();
_recursiveCount = 0;
}
public void Lock()
{
if (!IsLockedByCurrentThread())
{
_cs.Enter();
}
_recursiveCount++;
Abort.DoAbortUnless(_recursiveCount != 0);
}
public bool TryLock()
{
if (!IsLockedByCurrentThread())
{
if (!_cs.TryEnter())
{
return false;
}
}
_recursiveCount++;
Abort.DoAbortUnless(_recursiveCount != 0);
return true;
}
public void Unlock()
{
Abort.DoAbortUnless(IsLockedByCurrentThread());
_recursiveCount--;
if (_recursiveCount == 0)
{
_cs.Leave();
}
}
public bool IsLockedByCurrentThread()
{
return _cs.IsLockedByCurrentThread();
}
}
}

12
src/LibHac/Os/Thread.cs Normal file
View File

@ -0,0 +1,12 @@
using LibHac.Os.Impl;
namespace LibHac.Os
{
public static class Thread
{
public static void SleepThread(this OsState os, TimeSpan time)
{
TimeoutHelperImpl.Sleep(os, time);
}
}
}

39
src/LibHac/Os/Tick.cs Normal file
View File

@ -0,0 +1,39 @@
using System;
using LibHac.Os.Impl;
namespace LibHac.Os
{
public readonly struct Tick : IEquatable<Tick>
{
private readonly long _ticks;
public Tick(long ticks) => _ticks = ticks;
public long GetInt64Value() => _ticks;
public TimeSpan ToTimeSpan(OsState os) => os.ConvertToTimeSpan(this);
public static Tick operator +(Tick left, Tick right) => new(left._ticks + right._ticks);
public static Tick operator -(Tick left, Tick right) => new(left._ticks - right._ticks);
public static bool operator ==(Tick left, Tick right) => left._ticks == right._ticks;
public static bool operator !=(Tick left, Tick right) => left._ticks != right._ticks;
public static bool operator <(Tick left, Tick right) => left._ticks < right._ticks;
public static bool operator >(Tick left, Tick right) => left._ticks > right._ticks;
public static bool operator <=(Tick left, Tick right) => left._ticks <= right._ticks;
public static bool operator >=(Tick left, Tick right) => left._ticks >= right._ticks;
public override bool Equals(object obj) => obj is Tick other && Equals(other);
public bool Equals(Tick other) => _ticks == other._ticks;
public override int GetHashCode() => _ticks.GetHashCode();
public override string ToString() => _ticks.ToString();
}
public static class TickApi
{
public static Tick GetSystemTick(this OsState os) => os.GetTickManager().GetTick();
public static Tick GetSystemTickOrdered(this OsState os) => os.GetTickManager().GetSystemTickOrdered();
public static long GetSystemTickFrequency(this OsState os) => os.GetTickManager().GetTickFrequency();
public static TimeSpan ConvertToTimeSpan(this OsState os, Tick tick) => os.GetTickManager().ConvertToTimespan(tick);
public static Tick ConvertToTick(this OsState os, TimeSpan ts) => os.GetTickManager().ConvertToTick(ts);
}
}

View File

@ -18,7 +18,7 @@ namespace LibHac
private Stopwatch _watch;
private long _timedBytes;
private readonly TimeSpan _animationInterval = TimeSpan.FromSeconds(1.0 / 30);
private readonly System.TimeSpan _animationInterval = System.TimeSpan.FromSeconds(1.0 / 30);
private const string Animation = @"|/-\";
private string _currentText = string.Empty;
@ -87,7 +87,7 @@ namespace LibHac
public string GetRateString()
{
return Utilities.GetBytesReadable((long) (_timedBytes / _watch.Elapsed.TotalSeconds)) + "/s";
return Utilities.GetBytesReadable((long)(_timedBytes / _watch.Elapsed.TotalSeconds)) + "/s";
}
private void TimerHandler(object state)
@ -159,7 +159,7 @@ namespace LibHac
private void ResetTimer()
{
_timer.Change(_animationInterval, TimeSpan.FromMilliseconds(-1));
_timer.Change(_animationInterval, System.TimeSpan.FromMilliseconds(-1));
}
public void Dispose()

View File

@ -1,5 +1,4 @@
using System;
using System.Diagnostics;
using System.Diagnostics;
namespace LibHac
{
@ -7,7 +6,7 @@ namespace LibHac
{
private Stopwatch Timer = Stopwatch.StartNew();
public TimeSpan GetCurrent()
public System.TimeSpan GetCurrent()
{
return Timer.Elapsed;
}

85
src/LibHac/TimeSpan.cs Normal file
View File

@ -0,0 +1,85 @@
using System;
namespace LibHac
{
public readonly struct TimeSpan : IEquatable<TimeSpan>, IComparable<TimeSpan>
{
private readonly TimeSpanType _ts;
public TimeSpan(nint _) => _ts = new TimeSpanType();
public TimeSpan(TimeSpanType ts) => _ts = ts;
public static TimeSpan FromNanoSeconds(long nanoSeconds) => new(TimeSpanType.FromNanoSeconds(nanoSeconds));
public static TimeSpan FromMicroSeconds(long microSeconds) => new(TimeSpanType.FromMicroSeconds(microSeconds));
public static TimeSpan FromMilliSeconds(long milliSeconds) => new(TimeSpanType.FromMilliSeconds(milliSeconds));
public static TimeSpan FromSeconds(long seconds) => new(TimeSpanType.FromSeconds(seconds));
public static TimeSpan FromMinutes(long minutes) => new(TimeSpanType.FromMinutes(minutes));
public static TimeSpan FromHours(long hours) => new(TimeSpanType.FromHours(hours));
public static TimeSpan FromDays(long days) => new(TimeSpanType.FromDays(days));
public long GetNanoSeconds() => _ts.GetNanoSeconds();
public long GetMicroSeconds() => _ts.GetMicroSeconds();
public long GetMilliSeconds() => _ts.GetMilliSeconds();
public long GetSeconds() => _ts.GetSeconds();
public long GetMinutes() => _ts.GetMinutes();
public long GetHours() => _ts.GetHours();
public long GetDays() => _ts.GetDays();
public static bool operator ==(TimeSpan left, TimeSpan right) => left.Equals(right);
public static bool operator !=(TimeSpan left, TimeSpan right) => !(left == right);
public static bool operator <(TimeSpan left, TimeSpan right) => left._ts < right._ts;
public static bool operator >(TimeSpan left, TimeSpan right) => left._ts > right._ts;
public static bool operator <=(TimeSpan left, TimeSpan right) => left._ts <= right._ts;
public static bool operator >=(TimeSpan left, TimeSpan right) => left._ts >= right._ts;
public static TimeSpan operator +(TimeSpan left, TimeSpan right) => new(left._ts + right._ts);
public static TimeSpan operator -(TimeSpan left, TimeSpan right) => new(left._ts - right._ts);
public static implicit operator TimeSpanType(TimeSpan ts) => ts._ts;
public override bool Equals(object obj) => obj is TimeSpan ts && Equals(ts);
public bool Equals(TimeSpan other) => _ts == other._ts;
public override int GetHashCode() => _ts.GetHashCode();
public int CompareTo(TimeSpan other) => _ts.CompareTo(other._ts);
public override string ToString() => _ts.ToString();
}
public readonly struct TimeSpanType : IEquatable<TimeSpanType>, IComparable<TimeSpanType>
{
private readonly long _nanoSeconds;
private TimeSpanType(long nanoSeconds) => _nanoSeconds = nanoSeconds;
public static TimeSpanType FromNanoSeconds(long nanoSeconds) => new(nanoSeconds);
public static TimeSpanType FromMicroSeconds(long microSeconds) => new(microSeconds * 1000L);
public static TimeSpanType FromMilliSeconds(long milliSeconds) => new(milliSeconds * (1000L * 1000));
public static TimeSpanType FromSeconds(long seconds) => new(seconds * (1000L * 1000 * 1000));
public static TimeSpanType FromMinutes(long minutes) => new(minutes * (1000L * 1000 * 1000 * 60));
public static TimeSpanType FromHours(long hours) => new(hours * (1000L * 1000 * 1000 * 60 * 60));
public static TimeSpanType FromDays(long days) => new(days * (1000L * 1000 * 1000 * 60 * 60 * 24));
public long GetNanoSeconds() => _nanoSeconds;
public long GetMicroSeconds() => _nanoSeconds / 1000;
public long GetMilliSeconds() => _nanoSeconds / (1000L * 1000);
public long GetSeconds() => _nanoSeconds / (1000L * 1000 * 1000);
public long GetMinutes() => _nanoSeconds / (1000L * 1000 * 1000 * 60);
public long GetHours() => _nanoSeconds / (1000L * 1000 * 1000 * 60 * 60);
public long GetDays() => _nanoSeconds / (1000L * 1000 * 1000 * 60 * 60 * 24);
public static bool operator ==(TimeSpanType left, TimeSpanType right) => left._nanoSeconds == right._nanoSeconds;
public static bool operator !=(TimeSpanType left, TimeSpanType right) => left._nanoSeconds != right._nanoSeconds;
public static bool operator <(TimeSpanType left, TimeSpanType right) => left._nanoSeconds < right._nanoSeconds;
public static bool operator >(TimeSpanType left, TimeSpanType right) => left._nanoSeconds > right._nanoSeconds;
public static bool operator <=(TimeSpanType left, TimeSpanType right) => left._nanoSeconds <= right._nanoSeconds;
public static bool operator >=(TimeSpanType left, TimeSpanType right) => left._nanoSeconds >= right._nanoSeconds;
public static TimeSpanType operator +(TimeSpanType left, TimeSpanType right) => new(left._nanoSeconds + right._nanoSeconds);
public static TimeSpanType operator -(TimeSpanType left, TimeSpanType right) => new(left._nanoSeconds - right._nanoSeconds);
public override bool Equals(object obj) => obj is TimeSpanType ts && Equals(ts);
public bool Equals(TimeSpanType other) => _nanoSeconds == other._nanoSeconds;
public override int GetHashCode() => (int)_nanoSeconds;
public int CompareTo(TimeSpanType other) => _nanoSeconds.CompareTo(other._nanoSeconds);
public override string ToString() => _nanoSeconds.ToString();
}
}

View File

@ -8,7 +8,7 @@ namespace hactoolnet
{
public class ConsoleAccessLog : IAccessLog
{
public void Log(Result result, TimeSpan startTime, TimeSpan endTime, int handleId, string message, [CallerMemberName] string caller = "")
public void Log(Result result, System.TimeSpan startTime, System.TimeSpan endTime, int handleId, string message, [CallerMemberName] string caller = "")
{
Console.WriteLine(AccessLogHelpers.BuildDefaultLogLine(result, startTime, endTime, handleId, message, caller));
}
@ -22,7 +22,7 @@ namespace hactoolnet
Logger = logger;
}
public void Log(Result result, TimeSpan startTime, TimeSpan endTime, int handleId, string message, [CallerMemberName] string caller = "")
public void Log(Result result, System.TimeSpan startTime, System.TimeSpan endTime, int handleId, string message, [CallerMemberName] string caller = "")
{
Logger.LogMessage(AccessLogHelpers.BuildDefaultLogLine(result, startTime, endTime, handleId, message, caller));
}
@ -37,7 +37,7 @@ namespace hactoolnet
Logger = logger;
}
public void Log(Result result, TimeSpan startTime, TimeSpan endTime, int handleId, string message, [CallerMemberName] string caller = "")
public void Log(Result result, System.TimeSpan startTime, System.TimeSpan endTime, int handleId, string message, [CallerMemberName] string caller = "")
{
Logger.WriteLine(AccessLogHelpers.BuildDefaultLogLine(result, startTime, endTime, handleId, message, caller));
}