mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-19 18:36:53 +02:00
4b0de1b672
* Simplify archive loading and harden filesystem integration * Restore directory entry name length assertion * Keep graphics-dependent filesystem tests out of CI --------- Co-authored-by: nmzik <Nmzik@mail.ru>
607 lines
13 KiB
C++
607 lines
13 KiB
C++
#include "common/file.h"
|
|
|
|
#include "common/archive.h"
|
|
#include "common/assert.h"
|
|
#include "common/common.h"
|
|
#include "common/platform/sysFileIO.h"
|
|
#include "common/platform/sysTimer.h"
|
|
#include "common/stringUtils.h"
|
|
|
|
#include <cstdarg>
|
|
#include <cstdio>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
// IWYU pragma: no_include <fileapi.h>
|
|
// IWYU pragma: no_include <windows.h>
|
|
// IWYU pragma: no_include <winbase.h>
|
|
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
#ifdef CreateDirectory
|
|
#undef CreateDirectory
|
|
#endif
|
|
#ifdef DeleteFile
|
|
#undef DeleteFile
|
|
#endif
|
|
#ifdef CopyFile
|
|
#undef CopyFile
|
|
#endif
|
|
#endif
|
|
|
|
namespace Common {
|
|
|
|
namespace {
|
|
|
|
SysFileTimeStruct DateTimeToFileTimeUtc(const DateTime& date_time) {
|
|
SysTimeStruct time {};
|
|
time.is_invalid = false;
|
|
|
|
int year = 0;
|
|
int month = 0;
|
|
int day = 0;
|
|
date_time.GetDate().Get(&year, &month, &day);
|
|
time.Year = year;
|
|
time.Month = month;
|
|
time.Day = day;
|
|
|
|
int hour = 0;
|
|
int minute = 0;
|
|
int second = 0;
|
|
int milliseconds = 0;
|
|
date_time.GetTime().Get(&hour, &minute, &second, &milliseconds);
|
|
time.Hour = hour;
|
|
time.Minute = minute;
|
|
time.Second = second;
|
|
time.Milliseconds = milliseconds;
|
|
|
|
SysFileTimeStruct file_time {};
|
|
SysSystemToFileTimeUtc(time, file_time);
|
|
return file_time;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
struct File::FilePrivate {
|
|
sys_file_t* f;
|
|
std::unique_ptr<ArchiveFile> archive;
|
|
};
|
|
|
|
File::File(): m_p(std::make_unique<FilePrivate>()) {
|
|
m_p->f = nullptr;
|
|
}
|
|
|
|
File::~File() {
|
|
Close();
|
|
}
|
|
|
|
File::File(const std::filesystem::path& name): m_p(std::make_unique<FilePrivate>()) {
|
|
m_p->f = nullptr;
|
|
|
|
Create(name);
|
|
}
|
|
|
|
File::File(const std::filesystem::path& name, Mode mode): m_p(std::make_unique<FilePrivate>()) {
|
|
m_p->f = nullptr;
|
|
|
|
Open(name, mode);
|
|
}
|
|
|
|
bool File::IsInvalid() const {
|
|
return m_p->f == nullptr && m_p->archive == nullptr;
|
|
}
|
|
|
|
bool File::Create(const std::filesystem::path& name) {
|
|
EXIT_IF(!IsInvalid());
|
|
|
|
if (IsArchivePath(name)) {
|
|
return false;
|
|
}
|
|
|
|
m_file_name = name;
|
|
|
|
m_p->f = SysFileCreate(name);
|
|
|
|
if (SysFileIsError(*m_p->f)) {
|
|
Close();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool File::Open(const std::filesystem::path& name, Mode mode) {
|
|
EXIT_IF(!IsInvalid());
|
|
|
|
m_file_name = name;
|
|
if (IsArchivePath(name)) {
|
|
if (mode != Mode::Read) {
|
|
return false;
|
|
}
|
|
m_p->archive = OpenArchiveFile(name);
|
|
return m_p->archive != nullptr;
|
|
}
|
|
|
|
switch (mode) {
|
|
case Mode::Read: m_p->f = SysFileOpenR(name); break;
|
|
case Mode::Write: m_p->f = SysFileOpenW(name); break;
|
|
case Mode::ReadWrite:
|
|
case Mode::WriteRead: m_p->f = SysFileOpenRw(name); break;
|
|
}
|
|
|
|
if (SysFileIsError(*m_p->f)) {
|
|
Close();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool File::OpenInMem(void* buf, uint32_t buf_size) {
|
|
EXIT_IF(!IsInvalid());
|
|
|
|
m_p->f = SysFileOpen(static_cast<uint8_t*>(buf), buf_size);
|
|
|
|
if (SysFileIsError(*m_p->f)) {
|
|
Close();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool File::CreateInMem() {
|
|
EXIT_IF(!IsInvalid());
|
|
|
|
m_p->f = SysFileCreate();
|
|
|
|
if (SysFileIsError(*m_p->f)) {
|
|
Close();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void File::Close() {
|
|
m_p->archive.reset();
|
|
if (m_p->f != nullptr) {
|
|
SysFileClose(m_p->f);
|
|
m_p->f = nullptr;
|
|
}
|
|
}
|
|
|
|
uint64_t File::Size() const {
|
|
EXIT_IF(IsInvalid());
|
|
|
|
if (m_p->archive != nullptr) {
|
|
return m_p->archive->Size();
|
|
}
|
|
|
|
if (m_p->f == nullptr) {
|
|
return 0;
|
|
}
|
|
|
|
return SysFileSize(*m_p->f);
|
|
}
|
|
|
|
uint64_t File::Remaining() const {
|
|
EXIT_IF(IsInvalid());
|
|
|
|
const auto size = Size();
|
|
const auto position = Tell();
|
|
return position < size ? size - position : 0;
|
|
}
|
|
|
|
uint64_t File::Size(const std::filesystem::path& name) {
|
|
if (IsArchivePath(name)) {
|
|
const auto info = GetArchiveInfo(name);
|
|
return info ? info->size : 0;
|
|
}
|
|
return SysFileSize(name);
|
|
}
|
|
|
|
std::optional<File::Info> File::GetInfo(const std::filesystem::path& name) {
|
|
if (IsArchivePath(name)) {
|
|
return GetArchiveInfo(name);
|
|
}
|
|
Info info {};
|
|
return SysFileGetInfo(name, &info.is_file, &info.size) ? std::optional(info) : std::nullopt;
|
|
}
|
|
|
|
bool File::Seek(uint64_t offset) {
|
|
EXIT_IF(IsInvalid());
|
|
|
|
if (m_p->archive != nullptr) {
|
|
return m_p->archive->Seek(offset);
|
|
}
|
|
|
|
return SysFileSeek(*m_p->f, offset);
|
|
}
|
|
|
|
bool File::Truncate(uint64_t size) {
|
|
EXIT_IF(IsInvalid());
|
|
|
|
if (m_p->archive != nullptr) {
|
|
return false;
|
|
}
|
|
|
|
return SysFileTruncate(*m_p->f, size);
|
|
}
|
|
|
|
bool File::Unlink() {
|
|
EXIT_IF(IsInvalid());
|
|
|
|
if (m_p->archive != nullptr) {
|
|
return false;
|
|
}
|
|
|
|
return SysFileUnlink(*m_p->f, m_file_name);
|
|
}
|
|
|
|
uint64_t File::Tell() const {
|
|
EXIT_IF(IsInvalid());
|
|
|
|
if (m_p->archive != nullptr) {
|
|
return m_p->archive->Tell();
|
|
}
|
|
|
|
return SysFileTell(*m_p->f);
|
|
}
|
|
|
|
void File::Read(void* data, uint32_t size, uint32_t* bytes_read) {
|
|
EXIT_IF(IsInvalid());
|
|
|
|
if (m_p->archive != nullptr) {
|
|
m_p->archive->Read(data, size, bytes_read);
|
|
return;
|
|
}
|
|
|
|
if (m_p->f != nullptr) {
|
|
SysFileRead(data, size, *m_p->f, bytes_read);
|
|
}
|
|
}
|
|
|
|
void File::Write(const void* data, uint32_t size, uint32_t* bytes_written) {
|
|
EXIT_IF(IsInvalid());
|
|
|
|
if (m_p->archive != nullptr) {
|
|
if (bytes_written != nullptr) {
|
|
*bytes_written = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
SysFileWrite(data, size, *m_p->f, bytes_written);
|
|
}
|
|
|
|
static std::string FileVformat(const char* format, va_list args) {
|
|
va_list copy {};
|
|
va_copy(copy, args);
|
|
const int size = std::vsnprintf(nullptr, 0, format, copy);
|
|
va_end(copy);
|
|
|
|
if (size <= 0) {
|
|
return {};
|
|
}
|
|
|
|
std::vector<char> buffer(static_cast<size_t>(size) + 1u);
|
|
va_list copy2 {};
|
|
va_copy(copy2, args);
|
|
std::vsnprintf(buffer.data(), buffer.size(), format, copy2);
|
|
va_end(copy2);
|
|
|
|
return {buffer.data(), static_cast<size_t>(size)};
|
|
}
|
|
|
|
void File::Printf(const char* format, ...) {
|
|
va_list args {};
|
|
va_start(args, format);
|
|
|
|
auto s = FileVformat(format, args);
|
|
|
|
va_end(args);
|
|
|
|
s = Common::ReplaceStr(s, "\n", "\r\n");
|
|
Write(s.data(), static_cast<uint32_t>(s.size()));
|
|
}
|
|
|
|
static std::filesystem::path WithoutTrailingSeparator(
|
|
const std::filesystem::path& path) {
|
|
if (!path.empty() && !path.has_filename() &&
|
|
path != path.root_path()) {
|
|
return path.parent_path();
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
|
|
bool File::IsDirectoryExisting(const std::filesystem::path& path) {
|
|
if (IsArchivePath(path)) {
|
|
const auto info = GetArchiveInfo(path);
|
|
return info && !info->is_file;
|
|
}
|
|
return SysFileIsDirectoryExisting(WithoutTrailingSeparator(path));
|
|
}
|
|
|
|
bool File::IsFileExisting(const std::filesystem::path& name) {
|
|
if (IsArchivePath(name)) {
|
|
const auto info = GetArchiveInfo(name);
|
|
return info && info->is_file;
|
|
}
|
|
return SysFileIsFileExisting(name);
|
|
}
|
|
|
|
bool File::CreateDirectory(const std::filesystem::path& path) {
|
|
if (IsArchivePath(path)) {
|
|
return false;
|
|
}
|
|
return SysFileCreateDirectory(
|
|
WithoutTrailingSeparator(path));
|
|
}
|
|
|
|
bool File::DeleteDirectory(const std::filesystem::path& path) {
|
|
if (IsArchivePath(path)) {
|
|
return false;
|
|
}
|
|
return SysFileDeleteDirectory(
|
|
WithoutTrailingSeparator(path));
|
|
}
|
|
|
|
bool File::CreateDirectories(const std::filesystem::path& path) {
|
|
if (IsArchivePath(path)) {
|
|
return false;
|
|
}
|
|
const auto normalized_path =
|
|
WithoutTrailingSeparator(path);
|
|
|
|
auto current_path = normalized_path.root_path();
|
|
|
|
for (const auto& component:
|
|
normalized_path.relative_path()) {
|
|
current_path /= component;
|
|
|
|
if (IsDirectoryExisting(current_path)) {
|
|
continue;
|
|
}
|
|
|
|
if (!CreateDirectory(current_path)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool File::DeleteDirectories(const std::filesystem::path& path) {
|
|
if (IsArchivePath(path)) {
|
|
return false;
|
|
}
|
|
const auto normalized_path =
|
|
WithoutTrailingSeparator(path);
|
|
|
|
std::vector<std::filesystem::path> directories;
|
|
auto current_path = normalized_path.root_path();
|
|
|
|
for (const auto& component:
|
|
normalized_path.relative_path()) {
|
|
current_path /= component;
|
|
directories.push_back(current_path);
|
|
}
|
|
|
|
for (auto it = directories.rbegin();
|
|
it != directories.rend(); ++it) {
|
|
if (!DeleteDirectory(*it)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool File::DeleteFile(
|
|
const std::filesystem::path& name) // @suppress("Member declaration not found")
|
|
{
|
|
if (IsArchivePath(name)) {
|
|
return false;
|
|
}
|
|
return SysFileDeleteFile(name);
|
|
}
|
|
|
|
bool File::Flush() {
|
|
EXIT_IF(IsInvalid());
|
|
|
|
if (m_p->archive != nullptr) {
|
|
return true;
|
|
}
|
|
|
|
return SysFileFlush(*m_p->f);
|
|
}
|
|
|
|
std::vector<std::byte> File::ReadWholeBuffer() {
|
|
EXIT_IF(IsInvalid());
|
|
EXIT_IF(Tell() != 0);
|
|
|
|
uint64_t s = Size();
|
|
|
|
EXIT_IF((s >> 32u) != 0);
|
|
|
|
const auto read_size = static_cast<uint32_t>(s);
|
|
std::vector<std::byte> buf(read_size);
|
|
|
|
Read(buf.data(), read_size);
|
|
|
|
return buf;
|
|
}
|
|
|
|
DateTime File::GetLastAccessTimeUTC(const std::filesystem::path& name) {
|
|
if (IsArchivePath(name)) {
|
|
return GetLastAccessTimeUTC(GetArchiveHostPath(name));
|
|
}
|
|
SysTimeStruct t {};
|
|
SysFileToSystemTimeUtc(SysFileGetLastAccessTimeUtc(name), t);
|
|
|
|
if (t.is_invalid) {
|
|
return {};
|
|
}
|
|
|
|
return DateTime(Date(t.Year, t.Month, t.Day), Time(t.Hour, t.Minute, t.Second, t.Milliseconds));
|
|
}
|
|
|
|
DateTime File::GetLastWriteTimeUTC(const std::filesystem::path& name) {
|
|
if (IsArchivePath(name)) {
|
|
return GetLastWriteTimeUTC(GetArchiveHostPath(name));
|
|
}
|
|
SysTimeStruct t {};
|
|
SysFileToSystemTimeUtc(SysFileGetLastWriteTimeUtc(name), t);
|
|
|
|
if (t.is_invalid) {
|
|
return {};
|
|
}
|
|
|
|
return DateTime(Date(t.Year, t.Month, t.Day), Time(t.Hour, t.Minute, t.Second, t.Milliseconds));
|
|
}
|
|
|
|
void File::GetLastAccessAndWriteTimeUTC(const std::filesystem::path& name, DateTime* access,
|
|
DateTime* write) {
|
|
EXIT_IF(access == nullptr);
|
|
EXIT_IF(write == nullptr);
|
|
if (IsArchivePath(name)) {
|
|
GetLastAccessAndWriteTimeUTC(GetArchiveHostPath(name), access, write);
|
|
return;
|
|
}
|
|
|
|
SysTimeStruct at {};
|
|
SysTimeStruct wt {};
|
|
SysFileTimeStruct af {};
|
|
SysFileTimeStruct wf {};
|
|
|
|
SysFileGetLastAccessAndWriteTimeUtc(name, af, wf);
|
|
|
|
SysFileToSystemTimeUtc(af, at);
|
|
SysFileToSystemTimeUtc(wf, wt);
|
|
|
|
if (at.is_invalid || wt.is_invalid) {
|
|
*access = DateTime();
|
|
*write = DateTime();
|
|
} else {
|
|
*access = DateTime(Date(at.Year, at.Month, at.Day),
|
|
Time(at.Hour, at.Minute, at.Second, at.Milliseconds));
|
|
*write = DateTime(Date(wt.Year, wt.Month, wt.Day),
|
|
Time(wt.Hour, wt.Minute, wt.Second, wt.Milliseconds));
|
|
}
|
|
}
|
|
|
|
void File::GetLastAccessAndWriteTimeUTC(DateTime* access, DateTime* write) {
|
|
EXIT_IF(access == nullptr);
|
|
EXIT_IF(write == nullptr);
|
|
|
|
EXIT_IF(IsInvalid());
|
|
|
|
if (m_p->archive != nullptr) {
|
|
GetLastAccessAndWriteTimeUTC(m_file_name, access, write);
|
|
return;
|
|
}
|
|
|
|
if (m_p->f == nullptr) {
|
|
*access = DateTime();
|
|
*write = DateTime();
|
|
return;
|
|
}
|
|
|
|
SysTimeStruct at {};
|
|
SysTimeStruct wt {};
|
|
SysFileTimeStruct af {};
|
|
SysFileTimeStruct wf {};
|
|
|
|
SysFileGetLastAccessAndWriteTimeUtc(*m_p->f, af, wf);
|
|
|
|
SysFileToSystemTimeUtc(af, at);
|
|
SysFileToSystemTimeUtc(wf, wt);
|
|
|
|
if (at.is_invalid || wt.is_invalid) {
|
|
*access = DateTime();
|
|
*write = DateTime();
|
|
} else {
|
|
*access = DateTime(Date(at.Year, at.Month, at.Day),
|
|
Time(at.Hour, at.Minute, at.Second, at.Milliseconds));
|
|
*write = DateTime(Date(wt.Year, wt.Month, wt.Day),
|
|
Time(wt.Hour, wt.Minute, wt.Second, wt.Milliseconds));
|
|
}
|
|
}
|
|
|
|
bool File::SetLastAccessTimeUTC(const std::filesystem::path& name, const DateTime& dt) {
|
|
if (IsArchivePath(name)) {
|
|
return false;
|
|
}
|
|
auto f = DateTimeToFileTimeUtc(dt);
|
|
return SysFileSetLastAccessTimeUtc(name, f);
|
|
}
|
|
|
|
bool File::SetLastWriteTimeUTC(const std::filesystem::path& name, const DateTime& dt) {
|
|
if (IsArchivePath(name)) {
|
|
return false;
|
|
}
|
|
auto f = DateTimeToFileTimeUtc(dt);
|
|
return SysFileSetLastWriteTimeUtc(name, f);
|
|
}
|
|
|
|
bool File::SetLastAccessAndWriteTimeUTC(const std::filesystem::path& name, const DateTime& access,
|
|
const DateTime& write) {
|
|
if (IsArchivePath(name)) {
|
|
return false;
|
|
}
|
|
auto af = DateTimeToFileTimeUtc(access);
|
|
auto wf = DateTimeToFileTimeUtc(write);
|
|
return SysFileSetLastAccessAndWriteTimeUtc(name, af, wf);
|
|
}
|
|
|
|
std::vector<File::DirEntry> File::GetDirEntries(const std::filesystem::path& path) {
|
|
if (IsArchivePath(path)) {
|
|
return GetArchiveDirEntries(path);
|
|
}
|
|
std::vector<sys_dir_entry_t> files;
|
|
|
|
SysFileGetDents(path, files);
|
|
|
|
std::vector<File::DirEntry> ret;
|
|
ret.reserve(files.size());
|
|
|
|
for (auto& f: files) {
|
|
ret.emplace_back(std::move(f.name), f.is_file);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool File::CopyFile(const std::filesystem::path& src,
|
|
const std::filesystem::path& dst) // @suppress("Member declaration not found")
|
|
{
|
|
if (IsArchivePath(src) || IsArchivePath(dst)) {
|
|
return false;
|
|
}
|
|
return SysFileCopyFile(src, dst);
|
|
}
|
|
|
|
bool File::RenameFile(const std::filesystem::path& src,
|
|
const std::filesystem::path& dst) // @suppress("Member declaration not found")
|
|
{
|
|
if (IsArchivePath(src) || IsArchivePath(dst)) {
|
|
return false;
|
|
}
|
|
if (IsFileExisting(dst)) {
|
|
DeleteFile(dst); // @suppress("Invalid arguments")
|
|
}
|
|
|
|
return SysFileRenameFile(src, dst);
|
|
}
|
|
|
|
void File::RemoveReadonly(const std::filesystem::path& name) {
|
|
if (IsArchivePath(name)) {
|
|
return;
|
|
}
|
|
SysFileRemoveReadonly(name);
|
|
}
|
|
|
|
} // namespace Common
|