diff --git a/src/LibHac/FsSrv/Impl/AccessLogSdCardWriter.cs b/src/LibHac/FsSrv/Impl/AccessLogSdCardWriter.cs
new file mode 100644
index 00000000..a63ff031
--- /dev/null
+++ b/src/LibHac/FsSrv/Impl/AccessLogSdCardWriter.cs
@@ -0,0 +1,257 @@
+using System;
+using System.Buffers;
+using LibHac.Common;
+using LibHac.Fs;
+using LibHac.Fs.Fsa;
+using LibHac.Fs.Shim;
+using LibHac.Os;
+
+namespace LibHac.FsSrv.Impl;
+
+///
+/// Manages creating and opening the filesystem access log file on the SD card, and writes messages to the log file.
+///
+/// Based on nnSdk 18.3.0 (FS 18.0.0)
+public class AccessLogSdCardWriter : IDisposable
+{
+ private bool _isLogFileOpen;
+ private bool _isSetUp;
+ private FileHandle _fileHandle;
+ private byte[] _workBuffer;
+ private long _logFilePosition;
+ private int _bufferPosition;
+ private SdkMutex _mutex;
+
+ // Libhac addition
+ private FileSystemClient _fsClient;
+
+ private const int WorkBufferSize = 0x4000;
+
+ private ReadOnlySpan AccessLogMountName => "$FsAccessLog"u8;
+ private ReadOnlySpan AccessLogFilePath => "$FsAccessLog:/FsAccessLog.txt"u8;
+
+ private ReadOnlySpan BomUtf8 => [0xEF, 0xBB, 0xBF];
+ private ReadOnlySpan AccessLogStartMarker => "FS_ACCESS: { start_tag: true }\n"u8;
+ private ReadOnlySpan AccessLogEndMarker => "FS_ACCESS: { end_tag: true }\n"u8;
+
+ public AccessLogSdCardWriter(FileSystemClient fsClient)
+ {
+ _isLogFileOpen = false;
+ _isSetUp = false;
+ _workBuffer = null;
+ _logFilePosition = 0;
+ _bufferPosition = 0;
+ _mutex = new SdkMutex();
+ _fsClient = fsClient;
+ }
+
+ public void Dispose()
+ {
+ DeallocateWorkBuffer();
+ }
+
+ public void FinalizeObject()
+ {
+ using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex);
+
+ FlushBuffer();
+ TearDown(writeEndTag: true);
+ _isSetUp = true;
+
+ }
+
+ public void Flush()
+ {
+ using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex);
+
+ FlushBuffer();
+ TearDown(writeEndTag: true);
+ _isSetUp = false;
+ }
+
+ public void AppendLog(ReadOnlySpan buffer, ulong processId)
+ {
+ using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex);
+
+ if (buffer.Length > 0)
+ {
+ AppendProcessId(processId);
+ AppendBuffer(buffer);
+ }
+ }
+
+ private bool SetUp()
+ {
+ if (_isSetUp)
+ return _isLogFileOpen;
+
+ _isSetUp = true;
+ bool isSuccess = false;
+
+ Result res = _fsClient.MountSdCard(AccessLogMountName);
+ if (res.IsFailure()) return false;
+
+ try
+ {
+ res = _fsClient.CreateFile(AccessLogFilePath, 0);
+ if (res.IsSuccess() || ResultFs.PathAlreadyExists.Includes(res))
+ {
+ res = _fsClient.OpenFile(out _fileHandle, AccessLogFilePath, OpenMode.All);
+ if (res.IsSuccess())
+ {
+ try
+ {
+ if (WriteStartMarker())
+ {
+ _isLogFileOpen = true;
+ isSuccess = true;
+ return true;
+ }
+ }
+ finally
+ {
+ if (!isSuccess)
+ _fsClient.CloseFile(_fileHandle);
+ }
+ }
+ }
+ }
+ finally
+ {
+ if (!isSuccess)
+ _fsClient.Unmount(AccessLogMountName);
+ }
+
+ return false;
+ }
+
+ private bool WriteStartMarker()
+ {
+ Result res = _fsClient.GetFileSize(out _logFilePosition, _fileHandle);
+ if (res.IsFailure()) return false;
+
+ if (_logFilePosition <= 0)
+ {
+ res = _fsClient.WriteFile(_fileHandle, 0, BomUtf8, WriteOption.Flush);
+ if (res.IsFailure()) return false;
+
+ _logFilePosition = BomUtf8.Length;
+ }
+
+ res = _fsClient.WriteFile(_fileHandle, _logFilePosition, AccessLogStartMarker, WriteOption.Flush);
+ if (res.IsFailure()) return false;
+
+ _logFilePosition += AccessLogStartMarker.Length;
+ return true;
+ }
+
+ private void AppendBuffer(ReadOnlySpan buffer)
+ {
+ if (SetUp() && AllocateWorkBuffer())
+ {
+ if (buffer.Length > WorkBufferSize)
+ {
+ WriteWorkBuffer();
+ Write(buffer);
+ }
+ else
+ {
+ if ((long)_bufferPosition + buffer.Length > WorkBufferSize)
+ WriteWorkBuffer();
+
+ buffer.CopyTo(_workBuffer.AsSpan(_bufferPosition));
+ _bufferPosition += buffer.Length;
+ }
+ }
+ }
+
+ private void AppendProcessId(ulong processId)
+ {
+ Span buffer = stackalloc byte[0x16];
+
+ var sb = new U8StringBuilder(buffer);
+ sb.Append("(0x"u8).AppendFormat(processId, 'X', 16).Append(") "u8);
+
+ AppendBuffer(sb.Buffer);
+ }
+
+ private bool AllocateWorkBuffer()
+ {
+ if (_workBuffer is not null)
+ return true;
+
+ _workBuffer = ArrayPool.Shared.Rent(WorkBufferSize);
+ return _workBuffer is not null;
+ }
+
+ private void DeallocateWorkBuffer()
+ {
+ if (_workBuffer is not null)
+ {
+ ArrayPool.Shared.Return(_workBuffer);
+ _workBuffer = null;
+ }
+ }
+
+ private void TearDown(bool writeEndTag)
+ {
+ if (_isLogFileOpen)
+ {
+ if (writeEndTag)
+ {
+ _fsClient.WriteFile(_fileHandle, _logFilePosition, AccessLogEndMarker, WriteOption.Flush).IgnoreResult();
+ }
+
+ _fsClient.CloseFile(_fileHandle);
+ _fsClient.Unmount(AccessLogMountName);
+ _isLogFileOpen = false;
+ }
+ }
+
+ private void Write(ReadOnlySpan buffer)
+ {
+ if (_isLogFileOpen)
+ {
+ if (_logFilePosition + buffer.Length < _logFilePosition)
+ {
+ TearDown(writeEndTag: true);
+ }
+ else
+ {
+ Result res = _fsClient.WriteFile(_fileHandle, _logFilePosition, buffer, WriteOption.None);
+
+ if (res.IsSuccess())
+ {
+ _logFilePosition += buffer.Length;
+ }
+ else
+ {
+ TearDown(writeEndTag: false);
+ }
+ }
+ }
+ }
+
+ private void WriteWorkBuffer()
+ {
+ if (_workBuffer is not null && _bufferPosition > 0)
+ {
+ Write(_workBuffer.AsSpan(0, _bufferPosition));
+ _bufferPosition = 0;
+ }
+ }
+
+ private void FlushBuffer()
+ {
+ WriteWorkBuffer();
+
+ if (_isLogFileOpen)
+ {
+ Result res = _fsClient.FlushFile(_fileHandle);
+ if (res.IsFailure())
+ {
+ TearDown(writeEndTag: false);
+ }
+ }
+ }
+}
\ No newline at end of file