/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include "erpt_srv_allocator.hpp"
#include "erpt_srv_stream.hpp"
namespace ams::erpt::srv {
constinit bool Stream::s_can_access_fs = true;
constinit os::SdkMutex Stream::s_fs_commit_mutex;
void Stream::EnableFsAccess(bool en) {
s_can_access_fs = en;
}
Result Stream::DeleteStream(const char *path) {
R_UNLESS(s_can_access_fs, erpt::ResultInvalidPowerState());
return fs::DeleteFile(path);
}
Result Stream::CommitStream() {
R_UNLESS(s_can_access_fs, erpt::ResultInvalidPowerState());
std::scoped_lock lk(s_fs_commit_mutex);
fs::CommitSaveData(ReportStoragePath);
return ResultSuccess();
}
Result Stream::GetStreamSize(s64 *out, const char *path) {
R_UNLESS(s_can_access_fs, erpt::ResultInvalidPowerState());
fs::FileHandle file;
R_TRY(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Read));
ON_SCOPE_EXIT { fs::CloseFile(file); };
return fs::GetFileSize(out, file);
}
Stream::Stream() : buffer_size(0), file_position(0), buffer_count(0), buffer(nullptr), stream_mode(StreamMode_Invalid), initialized(false) {
/* ... */
}
Stream::~Stream() {
this->CloseStream();
AMS_ASSERT(!s_fs_commit_mutex.IsLockedByCurrentThread());
}
Result Stream::OpenStream(const char *path, StreamMode mode, u32 buffer_size) {
R_UNLESS(s_can_access_fs, erpt::ResultInvalidPowerState());
R_UNLESS(!this->initialized, erpt::ResultAlreadyInitialized());
auto lock_guard = SCOPE_GUARD {
if (s_fs_commit_mutex.IsLockedByCurrentThread()) {
s_fs_commit_mutex.Unlock();
}
};
if (mode == StreamMode_Write) {
s_fs_commit_mutex.Lock();
while (true) {
R_TRY_CATCH(fs::OpenFile(std::addressof(this->file_handle), path, fs::OpenMode_Write | fs::OpenMode_AllowAppend)) {
R_CATCH(fs::ResultPathNotFound) {
R_TRY(fs::CreateFile(path, 0));
continue;
}
} R_END_TRY_CATCH;
break;
}
fs::SetFileSize(this->file_handle, 0);
} else {
R_UNLESS(mode == StreamMode_Read, erpt::ResultInvalidArgument());
R_TRY(fs::OpenFile(std::addressof(this->file_handle), path, fs::OpenMode_Read));
}
auto file_guard = SCOPE_GUARD { fs::CloseFile(this->file_handle); };
std::strncpy(this->file_name, path, sizeof(this->file_name));
this->file_name[sizeof(this->file_name) - 1] = '\x00';
if (buffer_size > 0) {
this->buffer = reinterpret_cast(Allocate(buffer_size));
AMS_ASSERT(this->buffer != nullptr);
} else {
this->buffer = nullptr;
}
this->buffer_size = this->buffer != nullptr ? buffer_size : 0;
this->buffer_count = 0;
this->buffer_position = 0;
this->file_position = 0;
this->stream_mode = mode;
this->initialized = true;
file_guard.Cancel();
lock_guard.Cancel();
return ResultSuccess();
}
Result Stream::ReadStream(u32 *out, u8 *dst, u32 dst_size) {
R_UNLESS(s_can_access_fs, erpt::ResultInvalidPowerState());
R_UNLESS(this->initialized, erpt::ResultNotInitialized());
R_UNLESS(this->stream_mode == StreamMode_Read, erpt::ResultNotInitialized());
R_UNLESS(out != nullptr, erpt::ResultInvalidArgument());
R_UNLESS(dst != nullptr, erpt::ResultInvalidArgument());
size_t fs_read_size;
u32 read_count = 0;
ON_SCOPE_EXIT {
*out = read_count;
};
if (this->buffer != nullptr) {
while (dst_size > 0) {
if (u32 cur = std::min(this->buffer_count - this->buffer_position, dst_size); cur > 0) {
std::memcpy(dst, this->buffer + this->buffer_position, cur);
this->buffer_position += cur;
dst += cur;
dst_size -= cur;
read_count += cur;
} else {
R_TRY(fs::ReadFile(std::addressof(fs_read_size), this->file_handle, this->file_position, this->buffer, this->buffer_size));
this->buffer_position = 0;
this->file_position += static_cast(fs_read_size);
this->buffer_count = static_cast(fs_read_size);
if (this->buffer_count == 0) {
break;
}
}
}
} else {
R_TRY(fs::ReadFile(std::addressof(fs_read_size), this->file_handle, this->file_position, dst, dst_size));
this->file_position += static_cast(fs_read_size);
read_count = static_cast(fs_read_size);
}
return ResultSuccess();
}
Result Stream::WriteStream(const u8 *src, u32 src_size) {
R_UNLESS(s_can_access_fs, erpt::ResultInvalidPowerState());
R_UNLESS(this->initialized, erpt::ResultNotInitialized());
R_UNLESS(this->stream_mode == StreamMode_Write, erpt::ResultNotInitialized());
R_UNLESS(src != nullptr || src_size == 0, erpt::ResultInvalidArgument());
if (this->buffer != nullptr) {
while (src_size > 0) {
if (u32 cur = std::min(this->buffer_size - this->buffer_count, src_size); cur > 0) {
std::memcpy(this->buffer + this->buffer_count, src, cur);
this->buffer_count += cur;
src += cur;
src_size -= cur;
}
if (this->buffer_count == this->buffer_size) {
R_TRY(this->Flush());
}
}
} else {
R_TRY(fs::WriteFile(this->file_handle, this->file_position, src, src_size, fs::WriteOption::None));
this->file_position += src_size;
}
return ResultSuccess();
}
void Stream::CloseStream() {
if (this->initialized) {
if (s_can_access_fs) {
if (this->stream_mode == StreamMode_Write) {
this->Flush();
fs::FlushFile(this->file_handle);
}
fs::CloseFile(this->file_handle);
}
if (this->buffer != nullptr) {
Deallocate(this->buffer);
}
this->initialized = false;
if (s_fs_commit_mutex.IsLockedByCurrentThread()) {
s_fs_commit_mutex.Unlock();
}
}
}
Result Stream::GetStreamSize(s64 *out) const {
return GetStreamSize(out, this->file_name);
}
Result Stream::Flush() {
AMS_ASSERT(s_fs_commit_mutex.IsLockedByCurrentThread());
R_SUCCEED_IF(this->buffer_count == 0);
R_TRY(fs::WriteFile(this->file_handle, this->file_position, this->buffer, this->buffer_count, fs::WriteOption::None));
this->file_position += this->buffer_count;
this->buffer_count = 0;
return ResultSuccess();
}
}