mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 00:12:40 +00:00
Initial commit
This commit is contained in:
commit
147188b143
427 files changed
+205923
No files matched your search
@@ -0,0 +1,34 @@
|
||||
file(GLOB common_src CONFIGURE_DEPENDS
|
||||
"*.cpp"
|
||||
"logging/*.cpp"
|
||||
"platform/*.cpp"
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE common_headers CONFIGURE_DEPENDS "*.h")
|
||||
|
||||
if(KYTY_CLANG_CL)
|
||||
set_source_files_properties(${common_src} PROPERTIES COMPILE_FLAGS "-Wno-pragma-pack")
|
||||
endif()
|
||||
|
||||
add_library(common STATIC ${common_src} ${common_headers})
|
||||
|
||||
target_include_directories(common
|
||||
PUBLIC ${PROJECT_SOURCE_DIR}
|
||||
PRIVATE
|
||||
${KYTY_THIRD_PARTY_DIR}/cpuinfo/include
|
||||
)
|
||||
|
||||
target_link_libraries(common
|
||||
PUBLIC
|
||||
fmt::fmt
|
||||
Tracy::TracyClient
|
||||
PRIVATE
|
||||
cpuinfo
|
||||
spdlog::spdlog
|
||||
)
|
||||
|
||||
get_property(inc_headers TARGET common PROPERTY INCLUDE_DIRECTORIES)
|
||||
set(check_headers ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
clang_tidy_check(common "" "${check_headers}" "${inc_headers}")
|
||||
include_what_you_use(common "${inc_headers}")
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef KYTY_COMMON_ABI_H_
|
||||
#define KYTY_COMMON_ABI_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define KYTY_MS_ABI __attribute__((ms_abi))
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define KYTY_SYSV_ABI __attribute__((sysv_abi))
|
||||
|
||||
#endif /* KYTY_COMMON_ABI_H_ */
|
||||
@@ -0,0 +1,65 @@
|
||||
#include "common/assert.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/subsystems.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <fmt/format.h>
|
||||
#include <string>
|
||||
|
||||
namespace Common {
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && KYTY_BUILD == KYTY_BUILD_DEBUG && \
|
||||
KYTY_COMPILER == KYTY_COMPILER_CLANG
|
||||
constexpr int PRINT_STACK_FROM = 4;
|
||||
#else
|
||||
constexpr int PRINT_STACK_FROM = 2;
|
||||
#endif
|
||||
|
||||
static std::string BuildFatalReport(const char* title, std::string_view text, const char* file,
|
||||
int line) {
|
||||
DebugStack stack;
|
||||
DebugStack::Trace(&stack);
|
||||
|
||||
std::string report = "--- Stack Trace ---\n";
|
||||
for (int i = PRINT_STACK_FROM; i < stack.depth; i++) {
|
||||
report += fmt::format("[{}] {:016x}\n", i - PRINT_STACK_FROM,
|
||||
static_cast<uint64_t>(stack.GetAddr(i)));
|
||||
}
|
||||
report += fmt::format("{}\n{} in {}:{}\n", title, text, file, line);
|
||||
return report;
|
||||
}
|
||||
|
||||
static int DbgReport(const char* title, std::string_view text, const char* file, int line) {
|
||||
Log::WriteFatal(BuildFatalReport(title, text, file, line));
|
||||
SubsystemsListSingleton::Instance()->ShutdownAll();
|
||||
return 1;
|
||||
}
|
||||
|
||||
int DbgExitIfHandler(const char* expr, const char* file, int line) {
|
||||
return DbgReport("--- Fatal Error ---", fmt::format("Error: condition ({}) is true", expr),
|
||||
file, line);
|
||||
}
|
||||
|
||||
int DbgNotImplementedHandler(const char* expr, const char* file, int line) {
|
||||
return DbgReport("--- Fatal Error ---", fmt::format("Not implemented ({})", expr), file, line);
|
||||
}
|
||||
|
||||
int DbgExitHandler(const char* file, int line, std::string_view text) {
|
||||
Log::WriteFatal(BuildFatalReport("--- Error ---", text, file, line));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int DbgExitHandler(const char* file, int line, fmt::text_style style, std::string_view text) {
|
||||
Log::WriteFatal(style, BuildFatalReport("--- Error ---", text, file, line));
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DbgExit(int status) {
|
||||
std::fflush(nullptr);
|
||||
std::_Exit(status);
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,67 @@
|
||||
#ifndef KYTY_COMMON_ASSERT_H_
|
||||
#define KYTY_COMMON_ASSERT_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <string_view>
|
||||
|
||||
namespace Common {
|
||||
|
||||
#ifdef __clang__
|
||||
int DbgExitHandler(char const* file, int line, std::string_view text)
|
||||
__attribute__((analyzer_noreturn));
|
||||
int DbgExitHandler(char const* file, int line, fmt::text_style style, std::string_view text)
|
||||
__attribute__((analyzer_noreturn));
|
||||
int DbgExitIfHandler(char const* expr, char const* file, int line)
|
||||
__attribute__((analyzer_noreturn));
|
||||
int DbgNotImplementedHandler(char const* expr, char const* file, int line)
|
||||
__attribute__((analyzer_noreturn));
|
||||
void DbgExit(int status) __attribute__((analyzer_noreturn));
|
||||
#else
|
||||
int DbgExitHandler(char const* file, int line, std::string_view text);
|
||||
int DbgExitHandler(char const* file, int line, fmt::text_style style, std::string_view text);
|
||||
int DbgExitIfHandler(char const* expr, char const* file, int line);
|
||||
int DbgNotImplementedHandler(char const* expr, char const* file, int line);
|
||||
void DbgExit(int status);
|
||||
#endif
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS || KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
#define EXIT_HALT() (Common::DbgExit(321), 1)
|
||||
#else
|
||||
#define EXIT_HALT() (std::_Exit(321), 1)
|
||||
#endif
|
||||
|
||||
#ifndef KYTY_FINAL
|
||||
#define EXIT_IF(x) \
|
||||
((void)((x) && Common::DbgExitIfHandler(#x, __FILE__, __LINE__) != 0 && (EXIT_HALT(), 1) != 0))
|
||||
#else
|
||||
#define EXIT_IF(x) \
|
||||
do { \
|
||||
constexpr bool kyty_exit_if_disabled = false && (x); \
|
||||
(void)kyty_exit_if_disabled; \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#define EXIT(...) \
|
||||
do { \
|
||||
((void)(Common::DbgExitHandler(__FILE__, __LINE__, ::fmt::sprintf(__VA_ARGS__)) && \
|
||||
(EXIT_HALT(), 1))); \
|
||||
} while (0)
|
||||
|
||||
#define EXIT_COLOR(style, ...) \
|
||||
do { \
|
||||
((void)(Common::DbgExitHandler(__FILE__, __LINE__, (style), \
|
||||
::fmt::sprintf(__VA_ARGS__)) && \
|
||||
(EXIT_HALT(), 1))); \
|
||||
} while (0)
|
||||
|
||||
#define EXIT_NOT_IMPLEMENTED(x) \
|
||||
((void)((x) && Common::DbgNotImplementedHandler(#x, __FILE__, __LINE__) != 0 && \
|
||||
(EXIT_HALT(), 1) != 0))
|
||||
#define KYTY_NOT_IMPLEMENTED EXIT_NOT_IMPLEMENTED(true)
|
||||
|
||||
#endif /* KYTY_COMMON_ASSERT_H_ */
|
||||
@@ -0,0 +1,165 @@
|
||||
#ifndef KYTY_COMMON_BYTEBUFFER_H_
|
||||
#define KYTY_COMMON_BYTEBUFFER_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef> // IWYU pragma: export
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Common {
|
||||
|
||||
using Byte = std::byte;
|
||||
|
||||
class ByteBuffer {
|
||||
public:
|
||||
using DataType = std::vector<Byte>;
|
||||
|
||||
static constexpr uint32_t INVALID_INDEX = static_cast<uint32_t>(-1);
|
||||
|
||||
using iterator = DataType::iterator;
|
||||
using const_iterator = DataType::const_iterator;
|
||||
|
||||
ByteBuffer() = default;
|
||||
ByteBuffer(const ByteBuffer&) = default;
|
||||
ByteBuffer(ByteBuffer&&) = default;
|
||||
|
||||
ByteBuffer& operator=(const ByteBuffer&) = default;
|
||||
ByteBuffer& operator=(ByteBuffer&&) = default;
|
||||
|
||||
explicit ByteBuffer(uint32_t size): m_data(size) {}
|
||||
|
||||
ByteBuffer(std::initializer_list<uint8_t> list) {
|
||||
for (const uint8_t& e: list) {
|
||||
m_data.emplace_back(static_cast<Byte>(e));
|
||||
}
|
||||
}
|
||||
|
||||
explicit ByteBuffer(const void* buf, uint32_t size)
|
||||
: ByteBuffer(size) // @suppress("Ambiguous problem")
|
||||
{
|
||||
EXIT_IF(buf == nullptr && size != 0);
|
||||
|
||||
if (size != 0) {
|
||||
std::memcpy(m_data.data(), buf, size);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] uint32_t Size() const { return static_cast<uint32_t>(m_data.size()); }
|
||||
[[nodiscard]] bool IsEmpty() const { return m_data.empty(); }
|
||||
|
||||
void Clear() { m_data.clear(); }
|
||||
|
||||
void Add(const Byte& val) { m_data.push_back(val); }
|
||||
void Add(Byte&& val) { m_data.push_back(std::move(val)); }
|
||||
|
||||
void Add(const Byte* val, uint32_t num) {
|
||||
EXIT_IF(val == nullptr && num != 0);
|
||||
m_data.insert(m_data.end(), val, val + num);
|
||||
}
|
||||
|
||||
void Add(const ByteBuffer& v) {
|
||||
if (this == &v) {
|
||||
const ByteBuffer copy(v);
|
||||
Add(copy.GetDataConst(), copy.Size());
|
||||
return;
|
||||
}
|
||||
Add(v.GetDataConst(), v.Size());
|
||||
}
|
||||
|
||||
void InsertAt(uint32_t index, const Byte& val) {
|
||||
EXIT_IF(index > Size());
|
||||
m_data.insert(m_data.begin() + index, val);
|
||||
}
|
||||
|
||||
void InsertAt(uint32_t index, Byte&& val) {
|
||||
EXIT_IF(index > Size());
|
||||
m_data.insert(m_data.begin() + index, std::move(val));
|
||||
}
|
||||
|
||||
[[nodiscard]] uint32_t Find(const Byte& t) const {
|
||||
const auto it = std::find(m_data.begin(), m_data.end(), t);
|
||||
return (it == m_data.end() ? INVALID_INDEX
|
||||
: static_cast<uint32_t>(std::distance(m_data.begin(), it)));
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Contains(const Byte& t) const { return Find(t) != INVALID_INDEX; }
|
||||
|
||||
bool Remove(const Byte& t) {
|
||||
const auto it = std::find(m_data.begin(), m_data.end(), t);
|
||||
if (it == m_data.end()) {
|
||||
return false;
|
||||
}
|
||||
m_data.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RemoveAt(uint32_t index, uint32_t count = 1) {
|
||||
if (index >= Size() || count == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t erase_count = std::min(count, Size() - index);
|
||||
m_data.erase(m_data.begin() + index, m_data.begin() + index + erase_count);
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IndexValid(uint32_t index) const { return index < Size(); }
|
||||
|
||||
Byte& operator[](uint32_t index) {
|
||||
EXIT_IF(index >= Size());
|
||||
return m_data[index];
|
||||
}
|
||||
|
||||
const Byte& operator[](uint32_t index) const {
|
||||
EXIT_IF(index >= Size());
|
||||
return m_data[index];
|
||||
}
|
||||
|
||||
[[nodiscard]] const Byte& At(uint32_t index) const {
|
||||
EXIT_IF(index >= Size());
|
||||
return m_data[index];
|
||||
}
|
||||
|
||||
Byte* GetData() { return m_data.data(); }
|
||||
[[nodiscard]] const Byte* GetData() const { return m_data.data(); }
|
||||
[[nodiscard]] const Byte* GetDataConst() const { return m_data.data(); }
|
||||
|
||||
void Sort() {
|
||||
std::sort(m_data.begin(), m_data.end(), [](Byte a, Byte b) {
|
||||
return std::to_integer<uint8_t>(a) < std::to_integer<uint8_t>(b);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename OP>
|
||||
void Sort(OP&& comp_func) {
|
||||
std::sort(m_data.begin(), m_data.end(), std::forward<OP>(comp_func));
|
||||
}
|
||||
|
||||
bool operator==(const ByteBuffer& s) const { return m_data == s.m_data; }
|
||||
bool operator!=(const ByteBuffer& s) const { return !(*this == s); }
|
||||
|
||||
iterator begin() { return m_data.begin(); } // NOLINT(readability-identifier-naming)
|
||||
iterator end() { return m_data.end(); } // NOLINT(readability-identifier-naming)
|
||||
[[nodiscard]] const_iterator begin() const {
|
||||
return m_data.begin();
|
||||
} // NOLINT(readability-identifier-naming)
|
||||
[[nodiscard]] const_iterator end() const {
|
||||
return m_data.end();
|
||||
} // NOLINT(readability-identifier-naming)
|
||||
[[nodiscard]] const_iterator cbegin() const {
|
||||
return m_data.cbegin();
|
||||
} // NOLINT(readability-identifier-naming)
|
||||
[[nodiscard]] const_iterator cend() const {
|
||||
return m_data.cend();
|
||||
} // NOLINT(readability-identifier-naming)
|
||||
|
||||
private:
|
||||
DataType m_data;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_BYTEBUFFER_H_ */
|
||||
@@ -0,0 +1,43 @@
|
||||
#ifndef KYTY_COMMON_COMMON_H_
|
||||
#define KYTY_COMMON_COMMON_H_
|
||||
|
||||
#include "common/config.h" // IWYU pragma: export
|
||||
|
||||
#if __cplusplus < 201703L
|
||||
#undef __cplusplus
|
||||
#define __cplusplus 201703L
|
||||
#endif
|
||||
|
||||
#if defined(__MINGW32__) || defined(__MINGW64__)
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage,cert-dcl51-cpp,cert-dcl37-c,bugprone-reserved-identifier)
|
||||
#define __USE_MINGW_ANSI_STDIO 1
|
||||
#endif
|
||||
|
||||
// IWYU pragma: begin_exports
|
||||
#include <cinttypes>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
// IWYU pragma: end_exports
|
||||
|
||||
#define KYTY_CLASS_NO_COPY(name) \
|
||||
public: \
|
||||
name(const name&) = delete; /* NOLINT(bugprone-macro-parentheses) */ \
|
||||
name& operator=(const name&) = delete; /* NOLINT(bugprone-macro-parentheses) */ \
|
||||
name(name&&) noexcept = delete; /* NOLINT(bugprone-macro-parentheses) */ \
|
||||
name& operator=(name&&) noexcept = delete; /* NOLINT(bugprone-macro-parentheses) */
|
||||
|
||||
#define KYTY_CLASS_DEFAULT_COPY(name) \
|
||||
public: \
|
||||
name(const name&) = default; /* NOLINT(bugprone-macro-parentheses) */ \
|
||||
name& operator=(const name&) = default; /* NOLINT(bugprone-macro-parentheses) */ \
|
||||
name(name&&) noexcept = default; /* NOLINT(bugprone-macro-parentheses) */ \
|
||||
name& operator=(name&&) noexcept = default; /* NOLINT(bugprone-macro-parentheses) */
|
||||
|
||||
#if KYTY_COMPILER == KYTY_COMPILER_GCC
|
||||
#define KYTY_FORMAT_PRINTF(a, b) __attribute__((format(gnu_printf, a, b)))
|
||||
#elif KYTY_COMPILER == KYTY_COMPILER_CLANG
|
||||
#define KYTY_FORMAT_PRINTF(a, b) __attribute__((format(printf, a, b)))
|
||||
#endif
|
||||
|
||||
#endif /* KYTY_COMMON_COMMON_H_ */
|
||||
@@ -0,0 +1,21 @@
|
||||
#include "common/commonSubsystem.h"
|
||||
|
||||
#include "common/byteBuffer.h" // IWYU pragma: associated
|
||||
#include "common/common.h" // IWYU pragma: associated
|
||||
#include "common/debug.h" // IWYU pragma: associated
|
||||
#include "common/hash.h" // IWYU pragma: associated
|
||||
#include "common/magicEnum.h" // IWYU pragma: associated
|
||||
#include "common/singleton.h" // IWYU pragma: associated
|
||||
#include "common/virtualMemory.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
KYTY_SUBSYSTEM_INIT(Common) {
|
||||
VirtualMemory::Init();
|
||||
}
|
||||
|
||||
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Common) {}
|
||||
|
||||
KYTY_SUBSYSTEM_DESTROY(Common) {}
|
||||
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef KYTY_COMMON_COMMON_SUBSYSTEM_H_
|
||||
#define KYTY_COMMON_COMMON_SUBSYSTEM_H_
|
||||
|
||||
#include "common/subsystems.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
KYTY_SUBSYSTEM_DEFINE(Common);
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_COMMON_SUBSYSTEM_H_ */
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef KYTY_COMMON_CONFIG_H_
|
||||
#define KYTY_COMMON_CONFIG_H_
|
||||
|
||||
#define KYTY_PLATFORM_WINDOWS 1
|
||||
#define KYTY_PLATFORM_LINUX 5
|
||||
|
||||
#define KYTY_COMPILER_GCC 1
|
||||
#define KYTY_COMPILER_CLANG 2
|
||||
|
||||
#define KYTY_LINKER_LD 1
|
||||
#define KYTY_LINKER_LLD 2
|
||||
#define KYTY_LINKER_LLD_LINK 3
|
||||
|
||||
#define KYTY_BUILD_DEBUG 1
|
||||
#define KYTY_BUILD_RELEASE 2
|
||||
|
||||
#define KYTY_ENDIAN_BIG 1
|
||||
#define KYTY_ENDIAN_LITTLE 2
|
||||
|
||||
#include "cmake_config.h"
|
||||
|
||||
#endif /* KYTY_COMMON_CONFIG_H_ */
|
||||
@@ -0,0 +1,694 @@
|
||||
#include "common/dateTime.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/platform/sysTimer.h"
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <class T>
|
||||
constexpr T FloorDiv(T a, T b) {
|
||||
if (b < 0) {
|
||||
if (a < 0) {
|
||||
return a / b;
|
||||
}
|
||||
b = -b;
|
||||
a = -a;
|
||||
}
|
||||
|
||||
return (a - (a < 0 ? b - 1 : 0)) / b;
|
||||
}
|
||||
|
||||
static void YmdToJd(int year, int month, int day, jd_t* jd) {
|
||||
jd_t t_year = year;
|
||||
jd_t t_month = month;
|
||||
jd_t t_day = day;
|
||||
|
||||
if (t_year < 0) {
|
||||
t_year++;
|
||||
}
|
||||
|
||||
jd_t a = FloorDiv<jd_t>(14 - t_month, 12);
|
||||
jd_t y = t_year + 4800 - a;
|
||||
jd_t m = t_month + 12 * a - 3;
|
||||
*jd = t_day + FloorDiv<jd_t>(153 * m + 2, 5) + 365 * y + FloorDiv<jd_t>(y, 4) -
|
||||
FloorDiv<jd_t>(y, 100) + FloorDiv<jd_t>(y, 400) - 32045;
|
||||
}
|
||||
|
||||
static void JdToYmd(int* year, int* month, int* day, jd_t jd) {
|
||||
jd_t a = jd + 32044;
|
||||
jd_t b = FloorDiv<jd_t>(4 * a + 3, 146097);
|
||||
jd_t c = a - FloorDiv<jd_t>(146097 * b, 4);
|
||||
jd_t d = FloorDiv<jd_t>(4 * c + 3, 1461);
|
||||
jd_t e = c - FloorDiv<jd_t>(1461 * d, 4);
|
||||
jd_t m = FloorDiv<jd_t>(5 * e + 2, 153);
|
||||
|
||||
int t_day = e - FloorDiv<jd_t>(153 * m + 2, 5) + 1;
|
||||
int t_month = m + 3 - 12 * FloorDiv<jd_t>(m, 10);
|
||||
int t_year = 100 * b + d - 4800 + FloorDiv<jd_t>(m, 10);
|
||||
|
||||
if (t_year <= 0) {
|
||||
t_year--;
|
||||
}
|
||||
|
||||
if (year != nullptr) {
|
||||
*year = t_year;
|
||||
}
|
||||
if (month != nullptr) {
|
||||
*month = t_month;
|
||||
}
|
||||
if (day != nullptr) {
|
||||
*day = t_day;
|
||||
}
|
||||
}
|
||||
|
||||
static void HmsToMs(int hour, int minute, int second, int msec, int* ms) {
|
||||
*ms = hour * 60 * 60 * 1000 + minute * 60 * 1000 + second * 1000 + msec;
|
||||
}
|
||||
|
||||
static void MsToHms(int* hour, int* minute, int* second, int* msec, int ms) {
|
||||
if (hour != nullptr) {
|
||||
*hour = ms / (60 * 60 * 1000);
|
||||
}
|
||||
if (minute != nullptr) {
|
||||
*minute = (ms % (60 * 60 * 1000)) / (60 * 1000);
|
||||
}
|
||||
if (second != nullptr) {
|
||||
*second = (ms / 1000) % 60;
|
||||
}
|
||||
if (msec != nullptr) {
|
||||
*msec = ms % 1000;
|
||||
}
|
||||
}
|
||||
|
||||
Date::Date(int year, int month, int day) {
|
||||
Set(year, month, day);
|
||||
}
|
||||
|
||||
bool Date::IsValid(int year, int month, int day) {
|
||||
if (year == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (day >= 1) &&
|
||||
(day <= DaysInMonth(month) || (day == 29 && month == 2 && IsLeapYear(year)));
|
||||
}
|
||||
|
||||
int Date::DaysInMonth() const {
|
||||
if (IsInvalid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int year = 0;
|
||||
int month = 0;
|
||||
JdToYmd(&year, &month, nullptr, m_jd);
|
||||
|
||||
if (month == 2 && IsLeapYear(year)) {
|
||||
return 29;
|
||||
}
|
||||
|
||||
return DaysInMonth(month);
|
||||
}
|
||||
|
||||
bool Date::IsLeapYear() const {
|
||||
if (IsInvalid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int year = 0;
|
||||
JdToYmd(&year, nullptr, nullptr, m_jd);
|
||||
|
||||
return IsLeapYear(year);
|
||||
}
|
||||
|
||||
int Date::DaysInMonth(int month) {
|
||||
static const int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||
|
||||
if (month < 1 || month > 12) {
|
||||
month = 0;
|
||||
}
|
||||
|
||||
return days[month];
|
||||
}
|
||||
|
||||
void Date::Set(int year, int month, int day) {
|
||||
if (IsValid(year, month, day)) {
|
||||
YmdToJd(year, month, day, &m_jd);
|
||||
} else {
|
||||
m_jd = DATE_JD_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
void Date::Get(int* year, int* month, int* day) const {
|
||||
if (IsInvalid()) {
|
||||
if (year != nullptr) {
|
||||
*year = 0;
|
||||
}
|
||||
if (month != nullptr) {
|
||||
*month = 0;
|
||||
}
|
||||
if (day != nullptr) {
|
||||
*day = 0;
|
||||
}
|
||||
} else {
|
||||
JdToYmd(year, month, day, m_jd);
|
||||
}
|
||||
}
|
||||
|
||||
bool Date::IsLeapYear(int year) {
|
||||
if (year < 1) {
|
||||
year++;
|
||||
}
|
||||
|
||||
return ((year % 4) == 0 && (year % 100) != 0) || (year % 400) == 0;
|
||||
}
|
||||
|
||||
int Date::Year() const {
|
||||
if (IsInvalid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int year = 0;
|
||||
JdToYmd(&year, nullptr, nullptr, m_jd);
|
||||
|
||||
return year;
|
||||
}
|
||||
|
||||
int Date::Month() const {
|
||||
if (IsInvalid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int month = 0;
|
||||
JdToYmd(nullptr, &month, nullptr, m_jd);
|
||||
|
||||
return month;
|
||||
}
|
||||
|
||||
int Date::Day() const {
|
||||
if (IsInvalid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int day = 0;
|
||||
JdToYmd(nullptr, nullptr, &day, m_jd);
|
||||
|
||||
return day;
|
||||
}
|
||||
|
||||
int Date::DaysInYear() const {
|
||||
if (IsInvalid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return IsLeapYear() ? 366 : 365;
|
||||
}
|
||||
|
||||
int Date::DayOfWeek() const {
|
||||
if (IsInvalid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (m_jd >= 0) {
|
||||
return (m_jd % 7) + 1;
|
||||
}
|
||||
|
||||
return ((m_jd + 1) % 7) + 7;
|
||||
}
|
||||
|
||||
int Date::DayOfYear() const {
|
||||
if (IsInvalid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
jd_t d = 0;
|
||||
YmdToJd(Year(), 1, 1, &d);
|
||||
|
||||
return m_jd - d + 1;
|
||||
}
|
||||
|
||||
int Date::DaysInYear(int year) {
|
||||
return IsLeapYear(year) ? 366 : 365;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-non-const-parameter)
|
||||
static bool FormatDate(const Date* d, const std::string& f, size_t* out_i, std::string* out_r) {
|
||||
static const char* month_short[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
||||
static const char* month_long[] = {"January", "February", "March", "April",
|
||||
"May", "June", "July", "August",
|
||||
"September", "October", "November", "December"};
|
||||
static const char* day_short[] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
|
||||
static const char* day_long[] = {"Monday", "Tuesday", "Wednesday", "Thursday",
|
||||
"Friday", "Saturday", "Sunday"};
|
||||
|
||||
auto& i = *out_i;
|
||||
auto& r = *out_r;
|
||||
if (f.compare(i, 4, "YYYY") == 0) {
|
||||
int y = d->Year();
|
||||
EXIT_IF(y < 0 || y > 9999);
|
||||
r += fmt::format("{:04d}", y);
|
||||
i += 3;
|
||||
} else if (f.compare(i, 3, "YYY") == 0) {
|
||||
int y = d->Year();
|
||||
EXIT_IF(y < 0 || y > 9999);
|
||||
r += Mid(fmt::format("{:04d}", y), 1);
|
||||
i += 2;
|
||||
} else if (f.compare(i, 2, "YY") == 0) {
|
||||
int y = d->Year();
|
||||
EXIT_IF(y < 0 || y > 9999);
|
||||
r += Mid(fmt::format("{:04d}", y), 2);
|
||||
i += 1;
|
||||
} else if (f.compare(i, 1, "Y") == 0) {
|
||||
int y = d->Year();
|
||||
EXIT_IF(y < 0 || y > 9999);
|
||||
r += Mid(fmt::format("{:04d}", y), 3);
|
||||
} else if (f.compare(i, 1, "Q") == 0) {
|
||||
r += fmt::format("{}", d->QuarterOfYear());
|
||||
} else if (f.compare(i, 2, "MM") == 0) {
|
||||
r += fmt::format("{:02d}", d->Month());
|
||||
i += 1;
|
||||
} else if (f.compare(i, 5, "MONTH") == 0) {
|
||||
r += month_long[d->Month() - 1];
|
||||
i += 4;
|
||||
} else if (f.compare(i, 3, "MON") == 0) {
|
||||
r += month_short[d->Month() - 1];
|
||||
i += 2;
|
||||
} else if (f.compare(i, 3, "DDD") == 0) {
|
||||
r += fmt::format("{:03d}", d->DayOfYear());
|
||||
i += 2;
|
||||
} else if (f.compare(i, 2, "DD") == 0) {
|
||||
r += fmt::format("{:02d}", d->Day());
|
||||
i += 1;
|
||||
} else if (f.compare(i, 2, "DY") == 0) {
|
||||
r += day_short[d->DayOfWeek() - 1];
|
||||
i += 1;
|
||||
} else if (f.compare(i, 3, "DAY") == 0) {
|
||||
r += day_long[d->DayOfWeek() - 1];
|
||||
i += 2;
|
||||
} else if (f.compare(i, 1, "D") == 0) {
|
||||
r += fmt::format("{}", d->DayOfWeek());
|
||||
} else if (f.compare(i, 1, "J") == 0) {
|
||||
r += fmt::format("{}", d->JulianDay());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* YYYY - 4-digit year
|
||||
* YYY, YY, Y - Last 3, 2, or 1 digit(s) of year.
|
||||
* Q - Quarter of year (1, 2, 3, 4; JAN-MAR = 1).
|
||||
* MM - Month (01-12; JAN = 01).
|
||||
* MON - Abbreviated name of month.
|
||||
* MONTH - Name of month
|
||||
* D - Day of week (1-7).
|
||||
* DAY - Name of day.
|
||||
* DY - Abbreviated name of day.
|
||||
* DD - Day of month (01-31).
|
||||
* DDD - Day of year (001-366).
|
||||
* J - Julian day
|
||||
*/
|
||||
std::string Date::ToString(const char* format) const {
|
||||
if (IsInvalid()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string r;
|
||||
std::string f = format;
|
||||
|
||||
for (size_t i = 0; i < f.size(); i++) {
|
||||
if (!FormatDate(this, f, &i, &r)) {
|
||||
r += f[i];
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int Date::QuarterOfYear() const {
|
||||
if (IsInvalid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (Month() - 1) / 3 + 1;
|
||||
}
|
||||
|
||||
Date Date::FromMacros(const std::string& date) {
|
||||
auto lst = Split(date, ' ');
|
||||
|
||||
if (lst.size() != 3) {
|
||||
return {};
|
||||
}
|
||||
|
||||
static const char* month_str[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
||||
|
||||
int month = -1;
|
||||
for (int i = 0; i < 12; i++) {
|
||||
if (lst[0] == month_str[i]) {
|
||||
month = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (month < 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return Date(ToInt32(lst[2]), month, ToInt32(lst[1]));
|
||||
}
|
||||
|
||||
Time::Time(int hour, int minute, int second, int msec) {
|
||||
Set(hour, minute, second, msec);
|
||||
}
|
||||
|
||||
void Time::Set(int hour, int minute, int second, int msec) {
|
||||
if (IsValid(hour, minute, second, msec)) {
|
||||
HmsToMs(hour, minute, second, msec, &m_ms);
|
||||
} else {
|
||||
m_ms = TIME_MS_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
void Time::Get(int* hour, int* minute, int* second, int* msec) const {
|
||||
if (IsInvalid()) {
|
||||
if (hour != nullptr) {
|
||||
*hour = -1;
|
||||
}
|
||||
if (minute != nullptr) {
|
||||
*minute = -1;
|
||||
}
|
||||
if (second != nullptr) {
|
||||
*second = -1;
|
||||
}
|
||||
if (msec != nullptr) {
|
||||
*msec = -1;
|
||||
}
|
||||
} else {
|
||||
MsToHms(hour, minute, second, msec, m_ms);
|
||||
}
|
||||
}
|
||||
|
||||
bool Time::IsValid(int hour, int minute, int second, int msec) {
|
||||
return (hour >= 0 && hour <= 23) && (minute >= 0 && minute <= 59) &&
|
||||
(second >= 0 && second <= 59) && (msec >= 0 && msec <= 999);
|
||||
}
|
||||
|
||||
int Time::Hour24() const {
|
||||
if (IsInvalid()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int h = 0;
|
||||
MsToHms(&h, nullptr, nullptr, nullptr, m_ms);
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
int Time::Hour12() const {
|
||||
if (IsInvalid()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int h = 0;
|
||||
MsToHms(&h, nullptr, nullptr, nullptr, m_ms);
|
||||
|
||||
h = h % 12;
|
||||
|
||||
return h == 0 ? 12 : h;
|
||||
}
|
||||
|
||||
int Time::Minute() const {
|
||||
if (IsInvalid()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int m = 0;
|
||||
MsToHms(nullptr, &m, nullptr, nullptr, m_ms);
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
int Time::Second() const {
|
||||
if (IsInvalid()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int s = 0;
|
||||
MsToHms(nullptr, nullptr, &s, nullptr, m_ms);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
int Time::Msec() const {
|
||||
if (IsInvalid()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int m = 0;
|
||||
MsToHms(nullptr, nullptr, nullptr, &m, m_ms);
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
bool Time::IsAM() const {
|
||||
if (IsInvalid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int h = 0;
|
||||
MsToHms(&h, nullptr, nullptr, nullptr, m_ms);
|
||||
|
||||
return h < 12;
|
||||
}
|
||||
|
||||
bool Time::IsPM() const {
|
||||
if (IsInvalid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int h = 0;
|
||||
MsToHms(&h, nullptr, nullptr, nullptr, m_ms);
|
||||
|
||||
return h >= 12;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-non-const-parameter)
|
||||
static bool FormatTime(const Time* t, const std::string& f, size_t* out_i, std::string* out_r) {
|
||||
auto& i = *out_i;
|
||||
auto& r = *out_r;
|
||||
if (f.compare(i, 4, "HH24") == 0) {
|
||||
r += fmt::format("{:02d}", t->Hour24());
|
||||
i += 3;
|
||||
} else if (f.compare(i, 4, "HH12") == 0) {
|
||||
r += fmt::format("{:02d}", t->Hour12());
|
||||
i += 3;
|
||||
} else if (f.compare(i, 2, "HH") == 0) {
|
||||
r += fmt::format("{:02d}", t->Hour12());
|
||||
i += 1;
|
||||
} else if (f.compare(i, 2, "MI") == 0) {
|
||||
r += fmt::format("{:02d}", t->Minute());
|
||||
i += 1;
|
||||
} else if (f.compare(i, 5, "SSSSS") == 0) {
|
||||
r += fmt::format("{:05d}", t->MsecTotal() / 1000);
|
||||
i += 4;
|
||||
} else if (f.compare(i, 2, "SS") == 0) {
|
||||
r += fmt::format("{:02d}", t->Second());
|
||||
i += 1;
|
||||
} else if (f.compare(i, 3, "FFF") == 0) {
|
||||
r += fmt::format("{:03d}", t->Msec());
|
||||
i += 2;
|
||||
} else if (f.compare(i, 2, "AM") == 0) {
|
||||
r += t->IsAM() ? "AM" : "PM";
|
||||
i += 1;
|
||||
} else if (f.compare(i, 4, "A.M.") == 0) {
|
||||
r += t->IsAM() ? "A.M." : "P.M.";
|
||||
i += 3;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* HH - Hour of day (1-12)
|
||||
* HH12 - Hour of day (1-12)
|
||||
* HH24 - Hour of day (0-23)
|
||||
* MI - Minute (0-59)
|
||||
* SS - Second (0-59)
|
||||
* SSSSS - Seconds past midnight (0-86399)
|
||||
* FFF - Milliseconds
|
||||
* AM - AM or PM
|
||||
* A.M. - A.M. or P.M.
|
||||
*/
|
||||
std::string Time::ToString(const char* format) const {
|
||||
if (IsInvalid()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string r;
|
||||
std::string f = format;
|
||||
|
||||
for (size_t i = 0; i < f.size(); i++) {
|
||||
if (!FormatTime(this, f, &i, &r)) {
|
||||
r += f[i];
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
Time Time::operator+(int secs) const {
|
||||
int ms_secs = secs * 1000;
|
||||
|
||||
EXIT_IF(ms_secs > TIME_MS_IN_DAY || ms_secs < -TIME_MS_IN_DAY);
|
||||
|
||||
if (IsInvalid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
int r = m_ms + ms_secs;
|
||||
|
||||
if (r < 0) {
|
||||
r += TIME_MS_IN_DAY;
|
||||
}
|
||||
if (r >= TIME_MS_IN_DAY) {
|
||||
r -= TIME_MS_IN_DAY;
|
||||
}
|
||||
|
||||
return Time(r);
|
||||
}
|
||||
|
||||
Time Time::operator-(int secs) const {
|
||||
int ms_secs = secs * 1000;
|
||||
|
||||
EXIT_IF(ms_secs > TIME_MS_IN_DAY || ms_secs < -TIME_MS_IN_DAY);
|
||||
|
||||
if (IsInvalid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
int r = m_ms - ms_secs;
|
||||
|
||||
if (r < 0) {
|
||||
r += TIME_MS_IN_DAY;
|
||||
}
|
||||
if (r >= TIME_MS_IN_DAY) {
|
||||
r -= TIME_MS_IN_DAY;
|
||||
}
|
||||
|
||||
return Time(r);
|
||||
}
|
||||
|
||||
Time Time::operator+=(int secs) {
|
||||
int ms_secs = secs * 1000;
|
||||
|
||||
EXIT_IF(ms_secs > TIME_MS_IN_DAY || ms_secs < -TIME_MS_IN_DAY);
|
||||
|
||||
if (!IsInvalid()) {
|
||||
m_ms += ms_secs;
|
||||
|
||||
if (m_ms < 0) {
|
||||
m_ms += TIME_MS_IN_DAY;
|
||||
}
|
||||
if (m_ms >= TIME_MS_IN_DAY) {
|
||||
m_ms -= TIME_MS_IN_DAY;
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Time Time::operator-=(int secs) {
|
||||
int ms_secs = secs * 1000;
|
||||
|
||||
EXIT_IF(ms_secs > TIME_MS_IN_DAY || ms_secs < -TIME_MS_IN_DAY);
|
||||
|
||||
if (!IsInvalid()) {
|
||||
m_ms -= ms_secs;
|
||||
|
||||
if (m_ms < 0) {
|
||||
m_ms += TIME_MS_IN_DAY;
|
||||
}
|
||||
if (m_ms >= TIME_MS_IN_DAY) {
|
||||
m_ms -= TIME_MS_IN_DAY;
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
DateTime DateTime::FromSystem() {
|
||||
SysTimeStruct t {};
|
||||
SysGetSystemTime(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 DateTime::FromSystemUTC() {
|
||||
SysTimeStruct t {};
|
||||
SysGetSystemTimeUtc(t);
|
||||
|
||||
if (t.is_invalid) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return DateTime(Date(t.Year, t.Month, t.Day), Time(t.Hour, t.Minute, t.Second, t.Milliseconds));
|
||||
}
|
||||
|
||||
/**
|
||||
* YYYY - 4-digit year
|
||||
* YYY, YY, Y - Last 3, 2, or 1 digit(s) of year.
|
||||
* Q - Quarter of year (1, 2, 3, 4; JAN-MAR = 1).
|
||||
* MM - Month (01-12; JAN = 01).
|
||||
* MON - Abbreviated name of month.
|
||||
* MONTH - Name of month
|
||||
* D - Day of week (1-7).
|
||||
* DAY - Name of day.
|
||||
* DY - Abbreviated name of day.
|
||||
* DD - Day of month (01-31).
|
||||
* DDD - Day of year (001-366).
|
||||
* J - Julian day
|
||||
* HH - Hour of day (1-12)
|
||||
* HH12 - Hour of day (1-12)
|
||||
* HH24 - Hour of day (0-23)
|
||||
* MI - Minute (0-59)
|
||||
* SS - Second (0-59)
|
||||
* SSSSS - Seconds past midnight (0-86399)
|
||||
* FFF - Milliseconds
|
||||
* AM - AM or PM
|
||||
* A.M. - A.M. or P.M.
|
||||
*/
|
||||
std::string DateTime::ToString(const char* format) const {
|
||||
if (IsInvalid()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string r;
|
||||
std::string f = format;
|
||||
|
||||
for (size_t i = 0; i < f.size(); i++) {
|
||||
if (!FormatDate(&m_date, f, &i, &r) && !FormatTime(&m_time, f, &i, &r)) {
|
||||
r += f[i];
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
double DateTime::ToUnix() const {
|
||||
return (static_cast<double>(GetDate().JulianDay()) - 2440588.0) * 86400.0 +
|
||||
static_cast<double>(GetTime().MsecTotal()) / 1000.0;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,183 @@
|
||||
#ifndef KYTY_COMMON_DATETIME_H_
|
||||
#define KYTY_COMMON_DATETIME_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Common {
|
||||
|
||||
using jd_t = int32_t;
|
||||
|
||||
constexpr jd_t DATE_JD_INVALID = (INT32_MIN);
|
||||
constexpr int TIME_MS_INVALID = (-1);
|
||||
constexpr int TIME_MS_IN_DAY = (24 * 3600 * 1000);
|
||||
|
||||
constexpr int MONTH_JANUARY = 1;
|
||||
constexpr int MONTH_FEBRUARY = 2;
|
||||
constexpr int MONTH_MARCH = 3;
|
||||
constexpr int MONTH_APRIL = 4;
|
||||
constexpr int MONTH_MAY = 5;
|
||||
constexpr int MONTH_JUNE = 6;
|
||||
constexpr int MONTH_JULY = 7;
|
||||
constexpr int MONTH_AUGUST = 8;
|
||||
constexpr int MONTH_SEPTEMBER = 9;
|
||||
constexpr int MONTH_OCTOBER = 10;
|
||||
constexpr int MONTH_NOVEMBER = 11;
|
||||
constexpr int MONTH_DECEMBER = 12;
|
||||
|
||||
class Date final {
|
||||
public:
|
||||
Date() = default;
|
||||
explicit Date(jd_t d): m_jd(d) {}
|
||||
Date(const Date& d) = default;
|
||||
Date(Date&& d) noexcept = default;
|
||||
explicit Date(int year, int month, int day); // month in [1...12], day in [1...31]
|
||||
~Date() = default;
|
||||
|
||||
static Date FromMacros(const std::string& date);
|
||||
|
||||
void Set(int year, int month, int day);
|
||||
void Set(jd_t jd) { this->m_jd = jd; }
|
||||
void Get(int* year, int* month, int* day) const;
|
||||
|
||||
[[nodiscard]] bool IsInvalid() const { return m_jd == DATE_JD_INVALID; }
|
||||
[[nodiscard]] int DaysInMonth() const;
|
||||
[[nodiscard]] int DaysInYear() const;
|
||||
[[nodiscard]] bool IsLeapYear() const;
|
||||
[[nodiscard]] int Year() const;
|
||||
[[nodiscard]] int Month() const;
|
||||
[[nodiscard]] int Day() const;
|
||||
[[nodiscard]] jd_t JulianDay() const { return m_jd; }
|
||||
[[nodiscard]] int DayOfWeek() const;
|
||||
[[nodiscard]] int DayOfYear() const;
|
||||
[[nodiscard]] int QuarterOfYear() const;
|
||||
|
||||
std::string ToString(const char* format = "YYYY.MM.DD") const;
|
||||
|
||||
static bool IsValid(int year, int month, int day);
|
||||
static int DaysInMonth(int month);
|
||||
static int DaysInYear(int year);
|
||||
static bool IsLeapYear(int year);
|
||||
|
||||
bool operator==(const Date& other) const { return m_jd == other.m_jd; }
|
||||
bool operator!=(const Date& other) const { return m_jd != other.m_jd; }
|
||||
bool operator<(const Date& other) const { return m_jd < other.m_jd; }
|
||||
bool operator<=(const Date& other) const { return m_jd <= other.m_jd; }
|
||||
bool operator>(const Date& other) const { return m_jd > other.m_jd; }
|
||||
bool operator>=(const Date& other) const { return m_jd >= other.m_jd; }
|
||||
|
||||
Date& operator=(const Date& other) = default;
|
||||
Date& operator=(Date&& other) noexcept = default;
|
||||
Date operator+(int days) const { return Date(m_jd + days); }
|
||||
Date operator-(int days) const { return Date(m_jd - days); }
|
||||
Date operator+=(int days) {
|
||||
m_jd += days;
|
||||
return *this;
|
||||
}
|
||||
Date operator-=(int days) {
|
||||
m_jd -= days;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
jd_t m_jd {DATE_JD_INVALID};
|
||||
};
|
||||
|
||||
class Time final {
|
||||
public:
|
||||
Time() = default;
|
||||
explicit Time(int msec): m_ms(msec) {
|
||||
if (m_ms < 0 || m_ms >= TIME_MS_IN_DAY) {
|
||||
m_ms = TIME_MS_INVALID;
|
||||
}
|
||||
}
|
||||
Time(const Time& t) = default;
|
||||
Time(Time&& t) noexcept = default;
|
||||
explicit Time(int hour24, int minute, int second, int msec = 0);
|
||||
~Time() = default;
|
||||
|
||||
void Set(int hour24, int minute, int second, int msec = 0);
|
||||
void Set(int msec) { m_ms = msec; }
|
||||
void Get(int* hour24, int* minute, int* second, int* msec = nullptr) const;
|
||||
|
||||
[[nodiscard]] bool IsInvalid() const { return m_ms < 0 || m_ms >= TIME_MS_IN_DAY; }
|
||||
[[nodiscard]] int Hour12() const;
|
||||
[[nodiscard]] int Hour24() const;
|
||||
[[nodiscard]] bool IsAM() const;
|
||||
[[nodiscard]] bool IsPM() const;
|
||||
[[nodiscard]] int Minute() const;
|
||||
[[nodiscard]] int Second() const;
|
||||
[[nodiscard]] int Msec() const;
|
||||
[[nodiscard]] int MsecTotal() const { return m_ms; }
|
||||
|
||||
std::string ToString(const char* format = "HH24:MI:SS") const;
|
||||
|
||||
static bool IsValid(int hour, int minute, int second, int msec = 0);
|
||||
|
||||
bool operator==(const Time& other) const { return m_ms == other.m_ms; }
|
||||
bool operator!=(const Time& other) const { return m_ms != other.m_ms; }
|
||||
bool operator<(const Time& other) const { return m_ms < other.m_ms; }
|
||||
bool operator<=(const Time& other) const { return m_ms <= other.m_ms; }
|
||||
bool operator>(const Time& other) const { return m_ms > other.m_ms; }
|
||||
bool operator>=(const Time& other) const { return m_ms >= other.m_ms; }
|
||||
|
||||
Time& operator=(const Time& other) = default;
|
||||
Time& operator=(Time&& other) noexcept = default;
|
||||
Time operator+(int secs) const;
|
||||
Time operator-(int secs) const;
|
||||
Time operator+=(int secs);
|
||||
Time operator-=(int secs);
|
||||
|
||||
private:
|
||||
int m_ms {TIME_MS_INVALID};
|
||||
};
|
||||
|
||||
class DateTime final {
|
||||
public:
|
||||
DateTime() = default;
|
||||
explicit DateTime(const Date& d): m_date(d) {}
|
||||
explicit DateTime(const Time& t): m_time(t) {}
|
||||
explicit DateTime(const Date& d, const Time& t): m_date(d), m_time(t) {}
|
||||
DateTime(const DateTime& dt) = default;
|
||||
DateTime(DateTime&& dt) noexcept = default;
|
||||
~DateTime() = default;
|
||||
|
||||
static DateTime FromSystem();
|
||||
static DateTime FromSystemUTC();
|
||||
|
||||
[[nodiscard]] double ToUnix() const;
|
||||
|
||||
[[nodiscard]] bool IsInvalid() const { return m_date.IsInvalid() || m_time.IsInvalid(); }
|
||||
|
||||
void SetDate(const Date& d) { m_date = d; }
|
||||
void SetTime(const Time& t) { m_time = t; }
|
||||
Date& GetDate() { return m_date; }
|
||||
Time& GetTime() { return m_time; }
|
||||
[[nodiscard]] const Date& GetDate() const { return m_date; }
|
||||
[[nodiscard]] const Time& GetTime() const { return m_time; }
|
||||
|
||||
std::string ToString(const char* format = "YYYY.MM.DD HH24:MI:SS") const;
|
||||
|
||||
DateTime& operator=(const DateTime& other) = default;
|
||||
DateTime& operator=(DateTime&& other) noexcept = default;
|
||||
|
||||
bool operator==(const DateTime& other) const {
|
||||
return m_date == other.m_date && m_time == other.m_time;
|
||||
}
|
||||
bool operator!=(const DateTime& other) const { return !(*this == other); }
|
||||
bool operator<(const DateTime& other) const {
|
||||
return m_date < other.m_date || (m_date == other.m_date && m_time < other.m_time);
|
||||
}
|
||||
bool operator<=(const DateTime& other) const { return !(other < *this); }
|
||||
bool operator>(const DateTime& other) const { return other < *this; }
|
||||
bool operator>=(const DateTime& other) const { return !(*this < other); }
|
||||
|
||||
private:
|
||||
Date m_date;
|
||||
Time m_time;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_DATETIME_H_ */
|
||||
@@ -0,0 +1,44 @@
|
||||
#include "common/debug.h"
|
||||
|
||||
#include "common/platform/sysDbg.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
void DebugStack::Print(int from, [[maybe_unused]] bool with_name) const {
|
||||
for (int i = from; i < depth; i++) {
|
||||
printf("[%d] %016" PRIx64 "\n", i - from, static_cast<uint64_t>(GetAddr(i)));
|
||||
}
|
||||
}
|
||||
|
||||
void DebugStack::Trace(DebugStack* stack) {
|
||||
stack->depth = DEBUG_MAX_STACK_DEPTH;
|
||||
SysStackWalk(stack->stack.data(), &stack->depth);
|
||||
}
|
||||
|
||||
std::string Debug::GetCompiler() {
|
||||
#if KYTY_COMPILER == KYTY_COMPILER_CLANG
|
||||
return "clang";
|
||||
#elif KYTY_COMPILER == KYTY_COMPILER_GCC
|
||||
return "gcc";
|
||||
#else
|
||||
return "????";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string Debug::GetLinker() {
|
||||
#if KYTY_LINKER == KYTY_LINKER_LD
|
||||
return "ld";
|
||||
#elif KYTY_LINKER == KYTY_LINKER_LLD
|
||||
return "lld";
|
||||
#elif KYTY_LINKER == KYTY_LINKER_LLD_LINK
|
||||
return "lld_link";
|
||||
#else
|
||||
return "??";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string Debug::GetBitness() {
|
||||
return "64";
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef KYTY_COMMON_DEBUG_H_
|
||||
#define KYTY_COMMON_DEBUG_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace Common {
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && KYTY_BUILD == KYTY_BUILD_DEBUG && \
|
||||
KYTY_COMPILER == KYTY_COMPILER_CLANG
|
||||
constexpr int DEBUG_MAX_STACK_DEPTH = 20;
|
||||
#else
|
||||
constexpr int DEBUG_MAX_STACK_DEPTH = 15;
|
||||
#endif
|
||||
|
||||
namespace Debug {
|
||||
std::string GetCompiler();
|
||||
std::string GetLinker();
|
||||
std::string GetBitness();
|
||||
} // namespace Debug
|
||||
|
||||
struct DebugStack {
|
||||
int depth {0};
|
||||
std::array<void*, DEBUG_MAX_STACK_DEPTH> stack {};
|
||||
|
||||
[[nodiscard]] uintptr_t GetAddr(int i) const { return reinterpret_cast<uintptr_t>(stack[i]); }
|
||||
|
||||
void Print(int from, bool with_name = true) const;
|
||||
|
||||
void CopyTo(DebugStack* s) const {
|
||||
std::memcpy(s->stack.data(), stack.data(), sizeof(void*) * depth);
|
||||
s->depth = depth;
|
||||
}
|
||||
|
||||
static void Trace(DebugStack* stack);
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_DEBUG_H_ */
|
||||
@@ -0,0 +1,104 @@
|
||||
#include "common/emulatorConfig.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
namespace Config {
|
||||
|
||||
static std::unique_ptr<ConfigOptions> g_config;
|
||||
|
||||
KYTY_SUBSYSTEM_INIT(Config) {
|
||||
EXIT_IF(g_config != nullptr);
|
||||
|
||||
g_config = std::make_unique<ConfigOptions>();
|
||||
}
|
||||
|
||||
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Config) {}
|
||||
|
||||
KYTY_SUBSYSTEM_DESTROY(Config) {}
|
||||
|
||||
void Load(const ConfigOptions& cfg) {
|
||||
EXIT_IF(g_config == nullptr);
|
||||
|
||||
*g_config = cfg;
|
||||
}
|
||||
|
||||
uint32_t GetScreenWidth() {
|
||||
return g_config->screen_width;
|
||||
}
|
||||
|
||||
uint32_t GetScreenHeight() {
|
||||
return g_config->screen_height;
|
||||
}
|
||||
|
||||
uint32_t GetVblankFrequency() {
|
||||
return std::clamp(g_config->vblank_frequency, 30u, 360u);
|
||||
}
|
||||
|
||||
bool VulkanValidationEnabled() {
|
||||
return g_config->vulkan_validation_enabled;
|
||||
}
|
||||
|
||||
bool ShaderValidationEnabled() {
|
||||
return g_config->shader_validation_enabled;
|
||||
}
|
||||
|
||||
ShaderOptimizationType GetShaderOptimizationType() {
|
||||
return g_config->shader_optimization_type;
|
||||
}
|
||||
|
||||
ShaderLogDirection GetShaderLogDirection() {
|
||||
return g_config->shader_log_direction;
|
||||
}
|
||||
|
||||
std::filesystem::path GetShaderLogFolder() {
|
||||
return g_config->shader_log_folder;
|
||||
}
|
||||
|
||||
bool CommandBufferDumpEnabled() {
|
||||
return g_config->command_buffer_dump_enabled;
|
||||
}
|
||||
|
||||
std::filesystem::path GetCommandBufferDumpFolder() {
|
||||
return g_config->command_buffer_dump_folder;
|
||||
}
|
||||
|
||||
bool GraphicsDebugDumpEnabled() {
|
||||
return g_config->graphics_debug_dump_enabled;
|
||||
}
|
||||
|
||||
OutputDirection GetPrintfDirection() {
|
||||
return g_config->printf_direction;
|
||||
}
|
||||
|
||||
std::filesystem::path GetPrintfOutputFile() {
|
||||
return g_config->printf_output_file;
|
||||
}
|
||||
|
||||
ProfilerDirection GetProfilerDirection() {
|
||||
return g_config->profiler_direction;
|
||||
}
|
||||
|
||||
bool SpirvDebugPrintfEnabled() {
|
||||
return g_config->spirv_debug_printf_enabled;
|
||||
}
|
||||
|
||||
bool PipelineDumpEnabled() {
|
||||
return g_config->pipeline_dump_enabled;
|
||||
}
|
||||
|
||||
std::filesystem::path GetPipelineDumpFolder() {
|
||||
return g_config->pipeline_dump_folder;
|
||||
}
|
||||
|
||||
bool RenderDocEnabled() {
|
||||
return g_config->renderdoc_enabled;
|
||||
}
|
||||
|
||||
bool NggRectlistDrawEnabled() {
|
||||
return g_config->ngg_rectlist_draw_enabled;
|
||||
}
|
||||
|
||||
} // namespace Config
|
||||
@@ -0,0 +1,75 @@
|
||||
#ifndef KYTY_COMMON_EMULATOR_CONFIG_H_
|
||||
#define KYTY_COMMON_EMULATOR_CONFIG_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/subsystems.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace Config {
|
||||
|
||||
KYTY_SUBSYSTEM_DEFINE(Config);
|
||||
|
||||
enum class ShaderOptimizationType { None, Size, Performance };
|
||||
|
||||
enum class ShaderLogDirection { Silent, Console, File };
|
||||
|
||||
enum class ProfilerDirection { None, Network };
|
||||
|
||||
enum class OutputDirection { Silent, Console, File };
|
||||
|
||||
struct ConfigOptions {
|
||||
uint32_t screen_width = 1280;
|
||||
uint32_t screen_height = 720;
|
||||
uint32_t vblank_frequency = 60;
|
||||
bool vulkan_validation_enabled = false;
|
||||
bool shader_validation_enabled = false;
|
||||
ShaderOptimizationType shader_optimization_type = ShaderOptimizationType::None;
|
||||
ShaderLogDirection shader_log_direction = ShaderLogDirection::Silent;
|
||||
std::filesystem::path shader_log_folder = "_Shaders";
|
||||
bool command_buffer_dump_enabled = false;
|
||||
std::filesystem::path command_buffer_dump_folder = "_Buffers";
|
||||
bool graphics_debug_dump_enabled = false;
|
||||
OutputDirection printf_direction = OutputDirection::Console;
|
||||
std::filesystem::path printf_output_file = "_kyty.txt";
|
||||
ProfilerDirection profiler_direction = ProfilerDirection::None;
|
||||
bool spirv_debug_printf_enabled = false;
|
||||
bool pipeline_dump_enabled = false;
|
||||
std::filesystem::path pipeline_dump_folder = "_Pipelines";
|
||||
bool renderdoc_enabled = false;
|
||||
bool ngg_rectlist_draw_enabled = true;
|
||||
};
|
||||
|
||||
void Load(const ConfigOptions& cfg);
|
||||
|
||||
uint32_t GetScreenWidth();
|
||||
uint32_t GetScreenHeight();
|
||||
uint32_t GetVblankFrequency();
|
||||
bool VulkanValidationEnabled();
|
||||
|
||||
bool ShaderValidationEnabled();
|
||||
ShaderOptimizationType GetShaderOptimizationType();
|
||||
ShaderLogDirection GetShaderLogDirection();
|
||||
std::filesystem::path GetShaderLogFolder();
|
||||
|
||||
bool CommandBufferDumpEnabled();
|
||||
std::filesystem::path GetCommandBufferDumpFolder();
|
||||
|
||||
bool GraphicsDebugDumpEnabled();
|
||||
|
||||
OutputDirection GetPrintfDirection();
|
||||
std::filesystem::path GetPrintfOutputFile();
|
||||
|
||||
ProfilerDirection GetProfilerDirection();
|
||||
|
||||
bool SpirvDebugPrintfEnabled();
|
||||
|
||||
bool PipelineDumpEnabled();
|
||||
std::filesystem::path GetPipelineDumpFolder();
|
||||
|
||||
bool RenderDocEnabled();
|
||||
bool NggRectlistDrawEnabled();
|
||||
|
||||
} // namespace Config
|
||||
|
||||
#endif /* KYTY_COMMON_EMULATOR_CONFIG_H_ */
|
||||
@@ -0,0 +1,561 @@
|
||||
#include "common/file.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
|
||||
#ifdef MoveFile
|
||||
#undef MoveFile
|
||||
#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;
|
||||
};
|
||||
|
||||
File::File(): m_p(std::make_unique<FilePrivate>()) {
|
||||
m_p->f = nullptr;
|
||||
}
|
||||
|
||||
File::~File() {
|
||||
EXIT_IF(m_p->f != nullptr);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool File::Create(const std::filesystem::path& name) {
|
||||
EXIT_IF(m_p->f != nullptr);
|
||||
|
||||
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(m_p->f != nullptr);
|
||||
|
||||
m_file_name = name;
|
||||
|
||||
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(m_p->f != nullptr);
|
||||
|
||||
m_p->f = SysFileOpen(static_cast<uint8_t*>(buf), buf_size);
|
||||
|
||||
if (SysFileIsError(*m_p->f)) {
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::OpenInMem(ByteBuffer& buf) {
|
||||
return OpenInMem(buf.GetData(), buf.Size());
|
||||
}
|
||||
|
||||
bool File::CreateInMem() {
|
||||
EXIT_IF(m_p->f != nullptr);
|
||||
|
||||
m_p->f = SysFileCreate();
|
||||
|
||||
if (SysFileIsError(*m_p->f)) {
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void File::Close() {
|
||||
if (m_p->f != nullptr) {
|
||||
SysFileClose(m_p->f);
|
||||
m_p->f = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t File::Size() const {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
if (m_p->f == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return SysFileSize(*m_p->f);
|
||||
}
|
||||
|
||||
uint64_t File::Remaining() const {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
return Size() - Tell();
|
||||
}
|
||||
|
||||
uint64_t File::Size(const std::filesystem::path& name) {
|
||||
return SysFileSize(name);
|
||||
}
|
||||
|
||||
bool File::Seek(uint64_t offset) {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
return SysFileSeek(*m_p->f, offset);
|
||||
}
|
||||
|
||||
bool File::Truncate(uint64_t size) {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
return SysFileTruncate(*m_p->f, size);
|
||||
}
|
||||
|
||||
bool File::Unlink() {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
return SysFileUnlink(*m_p->f, m_file_name);
|
||||
}
|
||||
|
||||
uint64_t File::Tell() const {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
return SysFileTell(*m_p->f);
|
||||
}
|
||||
|
||||
void File::Read(void* data, uint32_t size, uint32_t* bytes_read) {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
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(m_p->f == nullptr);
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
bool File::IsDirectoryExisting(const std::filesystem::path& path) {
|
||||
auto path_str = PathToGenericString(path);
|
||||
return SysFileIsDirectoryExisting(
|
||||
Common::EndsWith(path_str, "/") ? Common::RemoveLast(path_str, 1) : path_str);
|
||||
}
|
||||
|
||||
bool File::IsFileExisting(const std::filesystem::path& name) {
|
||||
return SysFileIsFileExisting(name);
|
||||
}
|
||||
|
||||
bool File::CreateDirectory(
|
||||
const std::filesystem::path& path) // @suppress("Member declaration not found")
|
||||
{
|
||||
auto path_str = PathToGenericString(path);
|
||||
return SysFileCreateDirectory(Common::EndsWith(path_str, "/") ? Common::RemoveLast(path_str, 1)
|
||||
: path_str);
|
||||
}
|
||||
|
||||
bool File::DeleteDirectory(const std::filesystem::path& path) {
|
||||
auto path_str = PathToGenericString(path);
|
||||
return SysFileDeleteDirectory(Common::EndsWith(path_str, "/") ? Common::RemoveLast(path_str, 1)
|
||||
: path_str);
|
||||
}
|
||||
|
||||
bool File::CreateDirectories(const std::filesystem::path& path) {
|
||||
std::string real_path = Common::ReplaceChar(PathToGenericString(path), '\\', '/');
|
||||
|
||||
std::vector<std::string> list = Common::Split(real_path, "/");
|
||||
|
||||
std::string p;
|
||||
|
||||
for (uint32_t si = 0; si < list.size(); si++) {
|
||||
const std::string& s = list[si];
|
||||
|
||||
if (si != 0 || Common::StartsWith(real_path, "/")) {
|
||||
p += "/";
|
||||
}
|
||||
|
||||
p += s;
|
||||
|
||||
if (IsDirectoryExisting(p)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!CreateDirectory(p)) // @suppress("Invalid arguments")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::DeleteDirectories(const std::filesystem::path& path) {
|
||||
std::string real_path = Common::ReplaceChar(PathToGenericString(path), '\\', '/');
|
||||
|
||||
std::vector<std::string> list = Common::Split(real_path, "/");
|
||||
|
||||
std::string p;
|
||||
std::vector<std::string> list2;
|
||||
|
||||
for (uint32_t si = 0; si < list.size(); si++) {
|
||||
const std::string& s = list[si];
|
||||
|
||||
if (si != 0 || Common::StartsWith(real_path, "/")) {
|
||||
p += "/";
|
||||
}
|
||||
|
||||
p += s;
|
||||
|
||||
list2.push_back(p);
|
||||
}
|
||||
|
||||
uint32_t num = list2.size();
|
||||
|
||||
for (uint32_t si = num - 1; si < num; si--) {
|
||||
if (!DeleteDirectory(list2[si])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::DeleteFile(
|
||||
const std::filesystem::path& name) // @suppress("Member declaration not found")
|
||||
{
|
||||
return SysFileDeleteFile(name);
|
||||
}
|
||||
|
||||
bool File::Flush() {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
return SysFileFlush(*m_p->f);
|
||||
}
|
||||
|
||||
ByteBuffer File::ReadWholeBuffer() {
|
||||
EXIT_IF(IsInvalid());
|
||||
EXIT_IF(Tell() != 0);
|
||||
|
||||
uint64_t s = Size();
|
||||
|
||||
EXIT_IF((s >> 32u) != 0);
|
||||
|
||||
ByteBuffer buf(s);
|
||||
|
||||
Read(buf.GetData(), s);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
ByteBuffer File::Read(uint32_t size) {
|
||||
ByteBuffer buf(size);
|
||||
uint32_t b = 0;
|
||||
Read(buf.GetData(), size, &b);
|
||||
buf.RemoveAt(b, size - b);
|
||||
return buf;
|
||||
}
|
||||
|
||||
void File::Write(const ByteBuffer& buf, uint32_t* bytes_written) {
|
||||
Write(buf.GetDataConst(), buf.Size(), bytes_written);
|
||||
}
|
||||
|
||||
DateTime File::GetLastAccessTimeUTC(const std::filesystem::path& 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) {
|
||||
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);
|
||||
|
||||
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(m_p->f == nullptr);
|
||||
|
||||
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) {
|
||||
auto f = DateTimeToFileTimeUtc(dt);
|
||||
return SysFileSetLastAccessTimeUtc(name, f);
|
||||
}
|
||||
|
||||
bool File::SetLastWriteTimeUTC(const std::filesystem::path& name, const DateTime& dt) {
|
||||
auto f = DateTimeToFileTimeUtc(dt);
|
||||
return SysFileSetLastWriteTimeUtc(name, f);
|
||||
}
|
||||
|
||||
bool File::SetLastAccessAndWriteTimeUTC(const std::filesystem::path& name, const DateTime& access,
|
||||
const DateTime& write) {
|
||||
auto af = DateTimeToFileTimeUtc(access);
|
||||
auto wf = DateTimeToFileTimeUtc(write);
|
||||
return SysFileSetLastAccessAndWriteTimeUtc(name, af, wf);
|
||||
}
|
||||
|
||||
std::vector<File::FindInfo> File::FindFiles(const std::filesystem::path& path) {
|
||||
std::vector<sys_file_find_t> files;
|
||||
|
||||
SysFileFindFiles(path, files);
|
||||
|
||||
auto path_str = PathToGenericString(path);
|
||||
uint32_t len = static_cast<uint32_t>(path_str.size());
|
||||
|
||||
if (!Common::EndsWith(path_str, "/") && !Common::EndsWith(path_str, "\\")) {
|
||||
len++;
|
||||
}
|
||||
|
||||
std::vector<File::FindInfo> ret;
|
||||
ret.reserve(files.size());
|
||||
|
||||
for (const auto& f: files) {
|
||||
File::FindInfo r {};
|
||||
|
||||
r.path_with_name = f.path_with_name;
|
||||
r.rel_path_with_name = Common::Mid(PathToGenericString(f.path_with_name), len);
|
||||
r.size = f.size;
|
||||
|
||||
SysTimeStruct at {};
|
||||
SysTimeStruct wt {};
|
||||
|
||||
SysFileToSystemTimeUtc(f.last_access_time, at);
|
||||
SysFileToSystemTimeUtc(f.last_write_time, wt);
|
||||
|
||||
if (!at.is_invalid && !wt.is_invalid) {
|
||||
r.last_access_time = DateTime(Date(at.Year, at.Month, at.Day),
|
||||
Time(at.Hour, at.Minute, at.Second, at.Milliseconds));
|
||||
r.last_write_time = DateTime(Date(wt.Year, wt.Month, wt.Day),
|
||||
Time(wt.Hour, wt.Minute, wt.Second, wt.Milliseconds));
|
||||
}
|
||||
|
||||
// printf("%s, %s, %" PRIu64", %s, %s\n", r.path_with_name.c_str(),
|
||||
// r.rel_path_with_name.c_str(), r.size, r.last_access_time.ToString().c_str(),
|
||||
// r.last_write_time.ToString().c_str());
|
||||
|
||||
ret.push_back(std::move(r));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<File::DirEntry> File::GetDirEntries(const std::filesystem::path& path) {
|
||||
std::vector<sys_dir_entry_t> files;
|
||||
|
||||
SysFileGetDents(path, files);
|
||||
|
||||
std::vector<File::DirEntry> ret;
|
||||
ret.reserve(files.size());
|
||||
|
||||
for (const auto& f: files) {
|
||||
File::DirEntry r {};
|
||||
|
||||
r.name = f.name;
|
||||
r.is_file = f.is_file;
|
||||
|
||||
ret.push_back(std::move(r));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool File::CopyFile(const std::filesystem::path& src,
|
||||
const std::filesystem::path& dst) // @suppress("Member declaration not found")
|
||||
{
|
||||
return SysFileCopyFile(src, dst);
|
||||
}
|
||||
|
||||
bool File::MoveFile(const std::filesystem::path& src,
|
||||
const std::filesystem::path& dst) // @suppress("Member declaration not found")
|
||||
{
|
||||
if (IsFileExisting(dst)) {
|
||||
DeleteFile(dst); // @suppress("Invalid arguments")
|
||||
}
|
||||
|
||||
return SysFileMoveFile(src, dst);
|
||||
}
|
||||
|
||||
void File::RemoveReadonly(const std::filesystem::path& name) {
|
||||
SysFileRemoveReadonly(name);
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,124 @@
|
||||
#ifndef KYTY_COMMON_FILE_H_
|
||||
#define KYTY_COMMON_FILE_H_
|
||||
|
||||
#include "common/byteBuffer.h"
|
||||
#include "common/common.h"
|
||||
#include "common/dateTime.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#ifdef CreateDirectory
|
||||
#undef CreateDirectory
|
||||
#endif
|
||||
#ifdef DeleteFile
|
||||
#undef DeleteFile
|
||||
#endif
|
||||
#ifdef CopyFile
|
||||
#undef CopyFile
|
||||
#endif
|
||||
#ifdef MoveFile
|
||||
#undef MoveFile
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
// SUBSYSTEM_DEFINE(File);
|
||||
|
||||
class File {
|
||||
public:
|
||||
enum class Mode { Read, Write, ReadWrite, WriteRead };
|
||||
|
||||
struct FindInfo {
|
||||
std::filesystem::path path_with_name;
|
||||
std::filesystem::path rel_path_with_name;
|
||||
DateTime last_access_time;
|
||||
DateTime last_write_time;
|
||||
uint64_t size;
|
||||
};
|
||||
|
||||
struct DirEntry {
|
||||
std::string name;
|
||||
bool is_file;
|
||||
};
|
||||
|
||||
File();
|
||||
explicit File(const std::filesystem::path& name);
|
||||
File(const std::filesystem::path& name, Mode mode);
|
||||
virtual ~File();
|
||||
|
||||
bool Create(const std::filesystem::path& name);
|
||||
bool Open(const std::filesystem::path& name, Mode mode);
|
||||
bool OpenInMem(void* buf, uint32_t buf_size);
|
||||
bool OpenInMem(ByteBuffer& buf); // NOLINT(google-runtime-references)
|
||||
bool CreateInMem();
|
||||
|
||||
void Close();
|
||||
|
||||
bool Flush();
|
||||
|
||||
[[nodiscard]] uint64_t Size() const;
|
||||
[[nodiscard]] uint64_t Remaining() const;
|
||||
|
||||
bool Seek(uint64_t offset);
|
||||
[[nodiscard]] uint64_t Tell() const;
|
||||
|
||||
bool Truncate(uint64_t size);
|
||||
bool Unlink();
|
||||
|
||||
[[nodiscard]] bool IsInvalid() const;
|
||||
|
||||
[[nodiscard]] bool IsEOF() const { return Tell() >= Size(); }
|
||||
|
||||
void GetLastAccessAndWriteTimeUTC(DateTime* access, DateTime* write);
|
||||
|
||||
void Read(void* data, uint32_t size, uint32_t* bytes_read = nullptr);
|
||||
ByteBuffer Read(uint32_t size);
|
||||
void Write(const void* data, uint32_t size, uint32_t* bytes_written = nullptr);
|
||||
void Write(const ByteBuffer& buf, uint32_t* bytes_written = nullptr);
|
||||
|
||||
void Printf(const char* format, ...) KYTY_FORMAT_PRINTF(2, 3);
|
||||
|
||||
ByteBuffer ReadWholeBuffer();
|
||||
|
||||
static uint64_t Size(const std::filesystem::path& name);
|
||||
|
||||
static bool IsDirectoryExisting(const std::filesystem::path& path);
|
||||
static bool IsFileExisting(const std::filesystem::path& name);
|
||||
static bool CreateDirectory(const std::filesystem::path& path);
|
||||
static bool CreateDirectories(const std::filesystem::path& path);
|
||||
static bool DeleteDirectory(const std::filesystem::path& path);
|
||||
static bool DeleteDirectories(const std::filesystem::path& path);
|
||||
static bool DeleteFile(const std::filesystem::path& name);
|
||||
|
||||
static DateTime GetLastAccessTimeUTC(const std::filesystem::path& name);
|
||||
static DateTime GetLastWriteTimeUTC(const std::filesystem::path& name);
|
||||
static void GetLastAccessAndWriteTimeUTC(const std::filesystem::path& name, DateTime* access,
|
||||
DateTime* write);
|
||||
static bool SetLastAccessTimeUTC(const std::filesystem::path& name, const DateTime& dt);
|
||||
static bool SetLastWriteTimeUTC(const std::filesystem::path& name, const DateTime& dt);
|
||||
static bool SetLastAccessAndWriteTimeUTC(const std::filesystem::path& name,
|
||||
const DateTime& access, const DateTime& write);
|
||||
|
||||
static std::vector<FindInfo> FindFiles(const std::filesystem::path& path);
|
||||
static std::vector<DirEntry> GetDirEntries(const std::filesystem::path& path);
|
||||
static bool CopyFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
static bool MoveFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
static void RemoveReadonly(const std::filesystem::path& name);
|
||||
|
||||
KYTY_CLASS_NO_COPY(File);
|
||||
|
||||
private:
|
||||
struct FilePrivate;
|
||||
|
||||
std::filesystem::path m_file_name;
|
||||
std::unique_ptr<FilePrivate> m_p;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_FILE_H_ */
|
||||
@@ -0,0 +1,156 @@
|
||||
#ifndef KYTY_COMMON_HASH_H_
|
||||
#define KYTY_COMMON_HASH_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
inline uint32_t hash(const void* key, uint32_t key_len) {
|
||||
uint32_t hash = 0;
|
||||
|
||||
const auto* ptr = static_cast<const uint8_t*>(key);
|
||||
|
||||
while (key_len >= 4) {
|
||||
hash += ptr[0];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[1];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[2];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[3];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
|
||||
key_len -= 4;
|
||||
ptr += 4;
|
||||
}
|
||||
|
||||
switch (key_len) {
|
||||
case 3:
|
||||
hash += ptr[2];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
[[fallthrough]];
|
||||
case 2:
|
||||
hash += ptr[1];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
[[fallthrough]];
|
||||
case 1:
|
||||
hash += ptr[0];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
[[fallthrough]];
|
||||
default: break;
|
||||
}
|
||||
|
||||
hash += (hash << 3u);
|
||||
hash ^= (hash >> 11u);
|
||||
hash += (hash << 15u);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline uint32_t hash8(uint8_t key) {
|
||||
uint32_t hash = 0;
|
||||
|
||||
uint8_t* ptr = &key;
|
||||
|
||||
hash += ptr[0];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
|
||||
hash += (hash << 3u);
|
||||
hash ^= (hash >> 11u);
|
||||
hash += (hash << 15u);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline uint32_t hash16(uint16_t key) {
|
||||
uint32_t hash = 0;
|
||||
|
||||
auto* ptr = reinterpret_cast<uint8_t*>(&key);
|
||||
|
||||
hash += ptr[0];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[1];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
|
||||
hash += (hash << 3u);
|
||||
hash ^= (hash >> 11u);
|
||||
hash += (hash << 15u);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline uint32_t hash32(uint32_t key) {
|
||||
uint32_t hash = 0;
|
||||
|
||||
auto* ptr = reinterpret_cast<uint8_t*>(&key);
|
||||
|
||||
hash += ptr[0];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[1];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[2];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[3];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
|
||||
hash += (hash << 3u);
|
||||
hash ^= (hash >> 11u);
|
||||
hash += (hash << 15u);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline uint32_t hash64(uint64_t key) {
|
||||
uint32_t hash = 0;
|
||||
|
||||
auto* ptr = reinterpret_cast<uint8_t*>(&key);
|
||||
|
||||
hash += ptr[0];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[1];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[2];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[3];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[4];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[5];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[6];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[7];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
|
||||
hash += (hash << 3u);
|
||||
hash ^= (hash >> 11u);
|
||||
hash += (hash << 15u);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_HASH_H_ */
|
||||
@@ -0,0 +1,151 @@
|
||||
#include "common/hostException.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#include <windows.h> // IWYU pragma: keep
|
||||
#endif
|
||||
|
||||
// IWYU pragma: no_include <errhandlingapi.h>
|
||||
// IWYU pragma: no_include <excpt.h>
|
||||
// IWYU pragma: no_include <minwinbase.h>
|
||||
// IWYU pragma: no_include <minwindef.h>
|
||||
// IWYU pragma: no_include <wtypes.h>
|
||||
|
||||
namespace Common::HostException {
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
|
||||
static std::atomic<Handler> g_handler {nullptr};
|
||||
static std::atomic_uint32_t g_install_state {0};
|
||||
static thread_local bool g_in_exception_filter = false;
|
||||
|
||||
static_assert(decltype(g_handler)::is_always_lock_free);
|
||||
static_assert(decltype(g_install_state)::is_always_lock_free);
|
||||
|
||||
[[noreturn]] static void FailFast(const char* reason) noexcept {
|
||||
std::fputs("HostException fail-fast: ", stderr);
|
||||
std::fputs(reason != nullptr ? reason : "unspecified", stderr);
|
||||
std::fputc('\n', stderr);
|
||||
std::fflush(stderr);
|
||||
TerminateProcess(GetCurrentProcess(), static_cast<UINT>(EXCEPTION_NONCONTINUABLE_EXCEPTION));
|
||||
std::_Exit(321);
|
||||
}
|
||||
|
||||
class FilterScope final {
|
||||
public:
|
||||
FilterScope() noexcept {
|
||||
if (g_in_exception_filter) {
|
||||
FailFast("nested exception while resolving a host fault");
|
||||
}
|
||||
g_in_exception_filter = true;
|
||||
}
|
||||
|
||||
~FilterScope() { g_in_exception_filter = false; }
|
||||
|
||||
KYTY_CLASS_NO_COPY(FilterScope);
|
||||
};
|
||||
|
||||
static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) {
|
||||
FilterScope filter_scope;
|
||||
|
||||
auto* exception_record = exception->ExceptionRecord;
|
||||
|
||||
if (exception_record->ExceptionCode == DBG_PRINTEXCEPTION_C ||
|
||||
exception_record->ExceptionCode == DBG_PRINTEXCEPTION_WIDE_C) {
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
if (exception_record->ExceptionCode == 0x406D1388) {
|
||||
// Set a thread name.
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
|
||||
ExceptionInfo info {};
|
||||
info.exception_address = reinterpret_cast<uint64_t>(exception_record->ExceptionAddress);
|
||||
info.native_code = exception_record->ExceptionCode;
|
||||
info.native_context = exception->ContextRecord;
|
||||
|
||||
if (exception_record->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
|
||||
info.type = ExceptionType::AccessViolation;
|
||||
switch (exception_record->ExceptionInformation[0]) {
|
||||
case 0: info.access_violation_type = AccessViolationType::Read; break;
|
||||
case 1: info.access_violation_type = AccessViolationType::Write; break;
|
||||
case 8: info.access_violation_type = AccessViolationType::Execute; break;
|
||||
default: info.access_violation_type = AccessViolationType::Unknown; break;
|
||||
}
|
||||
info.access_violation_vaddr = exception_record->ExceptionInformation[1];
|
||||
} else if (exception_record->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION) {
|
||||
info.type = ExceptionType::IllegalInstruction;
|
||||
} else {
|
||||
printf("Unhandled win exception: code=0x%08" PRIx32 ", addr=0x%016" PRIx64
|
||||
", rip=0x%016" PRIx64 ", rsp=0x%016" PRIx64 ", rbp=0x%016" PRIx64 "\n",
|
||||
static_cast<uint32_t>(exception_record->ExceptionCode),
|
||||
reinterpret_cast<uint64_t>(exception_record->ExceptionAddress),
|
||||
exception->ContextRecord->Rip, exception->ContextRecord->Rsp,
|
||||
exception->ContextRecord->Rbp);
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
info.rax = exception->ContextRecord->Rax;
|
||||
info.rbx = exception->ContextRecord->Rbx;
|
||||
info.rcx = exception->ContextRecord->Rcx;
|
||||
info.rdx = exception->ContextRecord->Rdx;
|
||||
info.rsi = exception->ContextRecord->Rsi;
|
||||
info.rdi = exception->ContextRecord->Rdi;
|
||||
info.rbp = exception->ContextRecord->Rbp;
|
||||
info.rsp = exception->ContextRecord->Rsp;
|
||||
info.r8 = exception->ContextRecord->R8;
|
||||
info.r9 = exception->ContextRecord->R9;
|
||||
info.r10 = exception->ContextRecord->R10;
|
||||
info.r11 = exception->ContextRecord->R11;
|
||||
info.r12 = exception->ContextRecord->R12;
|
||||
info.r13 = exception->ContextRecord->R13;
|
||||
info.r14 = exception->ContextRecord->R14;
|
||||
info.r15 = exception->ContextRecord->R15;
|
||||
|
||||
if (g_install_state.load(std::memory_order_acquire) == 0) {
|
||||
FailFast("host exception handler is not installed");
|
||||
}
|
||||
|
||||
const auto handler = g_handler.load(std::memory_order_acquire);
|
||||
if (handler == nullptr) {
|
||||
FailFast("host exception callback is null");
|
||||
}
|
||||
|
||||
return handler(info) ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool InstallHandler(Handler handler) {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
if (handler == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t expected_state = 0;
|
||||
if (!g_install_state.compare_exchange_strong(expected_state, 1, std::memory_order_acq_rel)) {
|
||||
return expected_state == 2 && g_handler.load(std::memory_order_acquire) == handler;
|
||||
}
|
||||
|
||||
g_handler.store(handler, std::memory_order_release);
|
||||
|
||||
if (AddVectoredExceptionHandler(1, ExceptionFilter) == nullptr) {
|
||||
g_handler.store(nullptr, std::memory_order_release);
|
||||
g_install_state.store(0, std::memory_order_release);
|
||||
printf("AddVectoredExceptionHandler() failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
g_install_state.store(2, std::memory_order_release);
|
||||
return true;
|
||||
#else
|
||||
(void)handler;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Common::HostException
|
||||
@@ -0,0 +1,44 @@
|
||||
#ifndef KYTY_COMMON_HOST_EXCEPTION_H_
|
||||
#define KYTY_COMMON_HOST_EXCEPTION_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
namespace Common::HostException {
|
||||
|
||||
enum class ExceptionType { Unknown, AccessViolation, IllegalInstruction };
|
||||
|
||||
enum class AccessViolationType { Unknown, Read, Write, Execute };
|
||||
|
||||
struct ExceptionInfo {
|
||||
ExceptionType type = ExceptionType::Unknown;
|
||||
AccessViolationType access_violation_type = AccessViolationType::Unknown;
|
||||
uint64_t access_violation_vaddr = 0;
|
||||
uint64_t exception_address = 0;
|
||||
uint64_t rax = 0;
|
||||
uint64_t rbx = 0;
|
||||
uint64_t rcx = 0;
|
||||
uint64_t rdx = 0;
|
||||
uint64_t rsi = 0;
|
||||
uint64_t rdi = 0;
|
||||
uint64_t rbp = 0;
|
||||
uint64_t rsp = 0;
|
||||
uint64_t r8 = 0;
|
||||
uint64_t r9 = 0;
|
||||
uint64_t r10 = 0;
|
||||
uint64_t r11 = 0;
|
||||
uint64_t r12 = 0;
|
||||
uint64_t r13 = 0;
|
||||
uint64_t r14 = 0;
|
||||
uint64_t r15 = 0;
|
||||
uint32_t native_code = 0;
|
||||
// Platform-specific mutable context, valid only for the duration of the handler call.
|
||||
void* native_context = nullptr;
|
||||
};
|
||||
|
||||
using Handler = bool (*)(const ExceptionInfo&);
|
||||
|
||||
bool InstallHandler(Handler handler);
|
||||
|
||||
} // namespace Common::HostException
|
||||
|
||||
#endif /* KYTY_COMMON_HOST_EXCEPTION_H_ */
|
||||
@@ -0,0 +1,189 @@
|
||||
|
||||
#include "common/logging/log.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <fmt/format.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <spdlog/formatter.h>
|
||||
#include <spdlog/logger.h>
|
||||
#include <spdlog/sinks/basic_file_sink.h>
|
||||
#include <spdlog/sinks/null_sink.h>
|
||||
#include <spdlog/sinks/stdout_sinks.h>
|
||||
#include <string_view>
|
||||
|
||||
namespace {
|
||||
|
||||
class RawFormatter final: public spdlog::formatter {
|
||||
public:
|
||||
void format(const spdlog::details::log_msg& msg, spdlog::memory_buf_t& dest) override {
|
||||
const std::string_view payload(msg.payload.data(), msg.payload.size());
|
||||
dest.append(payload.data(), payload.data() + payload.size());
|
||||
}
|
||||
|
||||
std::unique_ptr<spdlog::formatter> clone() const override {
|
||||
return std::make_unique<RawFormatter>();
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<spdlog::logger> MakeLogger(std::string name, spdlog::sink_ptr sink) {
|
||||
sink->set_formatter(std::make_unique<RawFormatter>());
|
||||
|
||||
auto logger = std::make_shared<spdlog::logger>(std::move(name), std::move(sink));
|
||||
logger->set_level(spdlog::level::trace);
|
||||
return logger;
|
||||
}
|
||||
|
||||
std::shared_ptr<spdlog::logger> MakeFileLogger(std::string name,
|
||||
const std::filesystem::path& path) {
|
||||
const auto parent = path.parent_path();
|
||||
if (!parent.empty()) {
|
||||
std::filesystem::create_directories(parent);
|
||||
}
|
||||
|
||||
auto sink =
|
||||
std::make_shared<spdlog::sinks::basic_file_sink_mt>(Common::PathToString(path), true);
|
||||
return MakeLogger(std::move(name), std::move(sink));
|
||||
}
|
||||
|
||||
static bool HasStyle(fmt::text_style style) {
|
||||
return style != fmt::text_style {};
|
||||
}
|
||||
|
||||
void WriteStdout(std::string_view text, fmt::text_style style = {}) {
|
||||
if (HasStyle(style)) {
|
||||
fmt::print(stdout, style, "{}", text);
|
||||
} else {
|
||||
std::fwrite(text.data(), 1, text.size(), stdout);
|
||||
}
|
||||
std::fflush(stdout);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Log {
|
||||
|
||||
static bool g_initialized = false;
|
||||
static Direction g_direction = Direction::Console;
|
||||
static std::filesystem::path g_output_file;
|
||||
static std::mutex g_logger_mutex;
|
||||
static std::shared_ptr<spdlog::logger> g_logger;
|
||||
|
||||
void Flush() {
|
||||
if (g_logger != nullptr) {
|
||||
g_logger->flush();
|
||||
}
|
||||
}
|
||||
|
||||
static void SetupLogger() {
|
||||
std::lock_guard lock(g_logger_mutex);
|
||||
Flush();
|
||||
g_logger.reset();
|
||||
|
||||
switch (g_direction) {
|
||||
case Direction::Silent:
|
||||
g_logger = MakeLogger("kyty", std::make_shared<spdlog::sinks::null_sink_mt>());
|
||||
break;
|
||||
case Direction::Console:
|
||||
g_logger = MakeLogger("kyty", std::make_shared<spdlog::sinks::stdout_sink_mt>());
|
||||
break;
|
||||
case Direction::File:
|
||||
if (!g_output_file.empty()) {
|
||||
g_logger = MakeFileLogger("kyty", g_output_file);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static std::shared_ptr<spdlog::logger> GetLogger() {
|
||||
std::lock_guard lock(g_logger_mutex);
|
||||
return g_logger;
|
||||
}
|
||||
|
||||
static void WriteImpl(std::string_view text, fmt::text_style style = {}) {
|
||||
if (text.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!g_initialized) {
|
||||
WriteStdout(text, style);
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_direction == Direction::Silent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto logger = GetLogger()) {
|
||||
if (g_direction == Direction::Console && HasStyle(style)) {
|
||||
const auto styled = fmt::format(style, "{}", text);
|
||||
logger->log(spdlog::level::info, spdlog::string_view_t(styled.data(), styled.size()));
|
||||
} else {
|
||||
logger->log(spdlog::level::info, spdlog::string_view_t(text.data(), text.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WriteFatal(std::string_view text) {
|
||||
if (g_direction == Direction::Silent || !g_initialized) {
|
||||
WriteStdout(text);
|
||||
} else {
|
||||
WriteStdout(text);
|
||||
WriteImpl(text);
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
|
||||
void WriteFatal(fmt::text_style style, std::string_view text) {
|
||||
if (g_direction == Direction::Silent || !g_initialized) {
|
||||
WriteStdout(text, style);
|
||||
} else {
|
||||
WriteStdout(text, style);
|
||||
WriteImpl(text, style);
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
|
||||
KYTY_SUBSYSTEM_INIT(Log) {
|
||||
g_initialized = true;
|
||||
switch (Config::GetPrintfDirection()) {
|
||||
case Config::OutputDirection::Silent: g_direction = Direction::Silent; break;
|
||||
case Config::OutputDirection::Console: g_direction = Direction::Console; break;
|
||||
case Config::OutputDirection::File: g_direction = Direction::File; break;
|
||||
}
|
||||
g_output_file =
|
||||
(g_direction == Direction::File ? Config::GetPrintfOutputFile() : std::filesystem::path {});
|
||||
SetupLogger();
|
||||
}
|
||||
|
||||
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Log) {
|
||||
Flush();
|
||||
std::lock_guard lock(g_logger_mutex);
|
||||
g_logger.reset();
|
||||
}
|
||||
|
||||
KYTY_SUBSYSTEM_DESTROY(Log) {
|
||||
Flush();
|
||||
std::lock_guard lock(g_logger_mutex);
|
||||
g_logger.reset();
|
||||
}
|
||||
|
||||
Direction GetDirection() {
|
||||
EXIT_IF(!g_initialized);
|
||||
return g_direction;
|
||||
}
|
||||
|
||||
void Write(std::string_view text) {
|
||||
WriteImpl(text);
|
||||
}
|
||||
|
||||
void Write(fmt::text_style style, std::string_view text) {
|
||||
WriteImpl(text, style);
|
||||
}
|
||||
|
||||
} // namespace Log
|
||||
@@ -0,0 +1,48 @@
|
||||
#ifndef KYTY_COMMON_LOGGING_LOG_H_
|
||||
#define KYTY_COMMON_LOGGING_LOG_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/subsystems.h"
|
||||
|
||||
#include <fmt/color.h>
|
||||
#include <fmt/printf.h>
|
||||
#include <string_view>
|
||||
|
||||
namespace Log {
|
||||
|
||||
KYTY_SUBSYSTEM_DEFINE(Log);
|
||||
|
||||
enum class Direction { Silent, Console, File };
|
||||
|
||||
Direction GetDirection();
|
||||
void Write(std::string_view text);
|
||||
void Write(fmt::text_style style, std::string_view text);
|
||||
void WriteFatal(std::string_view text);
|
||||
void WriteFatal(fmt::text_style style, std::string_view text);
|
||||
void Flush();
|
||||
|
||||
namespace Color {
|
||||
|
||||
inline constexpr auto Default = fmt::text_style {};
|
||||
inline constexpr auto Red = fmt::fg(fmt::terminal_color::red);
|
||||
inline constexpr auto Green = fmt::fg(fmt::terminal_color::green);
|
||||
inline constexpr auto Yellow = fmt::fg(fmt::terminal_color::yellow);
|
||||
inline constexpr auto Magenta = fmt::fg(fmt::terminal_color::magenta);
|
||||
inline constexpr auto Cyan = fmt::fg(fmt::terminal_color::cyan);
|
||||
inline constexpr auto White = fmt::fg(fmt::terminal_color::white);
|
||||
inline constexpr auto BrightRed = fmt::fg(fmt::terminal_color::bright_red);
|
||||
inline constexpr auto BrightGreen = fmt::fg(fmt::terminal_color::bright_green);
|
||||
inline constexpr auto BrightYellow = fmt::fg(fmt::terminal_color::bright_yellow);
|
||||
inline constexpr auto BrightMagenta = fmt::fg(fmt::terminal_color::bright_magenta);
|
||||
inline constexpr auto BrightWhite = fmt::fg(fmt::terminal_color::bright_white);
|
||||
|
||||
} // namespace Color
|
||||
|
||||
} // namespace Log
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define LOGF(...) ::Log::Write(::fmt::sprintf(__VA_ARGS__))
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define LOGF_COLOR(style, ...) ::Log::Write((style), ::fmt::sprintf(__VA_ARGS__))
|
||||
|
||||
#endif /* KYTY_COMMON_LOGGING_LOG_H_ */
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef KYTY_COMMON_MAGICENUM_H_
|
||||
#define KYTY_COMMON_MAGICENUM_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "magic_enum.hpp" // IWYU pragma: export
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <typename E>
|
||||
inline std::string EnumName(E v) {
|
||||
auto str = magic_enum::enum_name(v);
|
||||
return std::string(str.data(), str.length());
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline std::string EnumName8(E v) {
|
||||
auto str = magic_enum::enum_name(v);
|
||||
return std::string(str.data(), str.length());
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline E EnumValue(const std::string& str, E default_value) {
|
||||
auto v = magic_enum::enum_cast<E>(str.c_str());
|
||||
if (v.has_value()) {
|
||||
return v.value();
|
||||
}
|
||||
return default_value;
|
||||
}
|
||||
|
||||
#define KYTY_ENUM_RANGE(e, mx, mn) \
|
||||
namespace magic_enum::customize { \
|
||||
template <> \
|
||||
struct enum_range<e> { \
|
||||
static constexpr int min = (mx); \
|
||||
static constexpr int max = (mn); \
|
||||
}; \
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_MAGICENUM_H_ */
|
||||
@@ -0,0 +1,33 @@
|
||||
#ifndef KYTY_COMMON_PLATFORM_SYSDBG_H_
|
||||
#define KYTY_COMMON_PLATFORM_SYSDBG_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct sys_dbg_stack_info_t {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
uintptr_t addr;
|
||||
|
||||
uintptr_t reserved_addr;
|
||||
size_t reserved_size;
|
||||
uintptr_t guard_addr;
|
||||
size_t guard_size;
|
||||
uintptr_t commited_addr;
|
||||
size_t commited_size;
|
||||
|
||||
size_t total_size;
|
||||
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
uintptr_t code_addr;
|
||||
uintptr_t addr;
|
||||
uintptr_t commited_addr;
|
||||
size_t commited_size;
|
||||
size_t total_size;
|
||||
size_t code_size;
|
||||
#endif
|
||||
};
|
||||
|
||||
void SysStackWalk(void** stack, int* depth);
|
||||
void SysStackUsage(sys_dbg_stack_info_t& s); // NOLINT(google-runtime-references)
|
||||
void SysStackUsagePrint(sys_dbg_stack_info_t& stack); // NOLINT(google-runtime-references)
|
||||
|
||||
#endif /* KYTY_COMMON_PLATFORM_SYSDBG_H_ */
|
||||
@@ -0,0 +1,86 @@
|
||||
#ifndef KYTY_COMMON_PLATFORM_SYSFILEIO_H_
|
||||
#define KYTY_COMMON_PLATFORM_SYSFILEIO_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/platform/sysTimer.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// The native file representation is private to the platform implementation.
|
||||
struct sys_file_t; // NOLINT(readability-identifier-naming)
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
enum sys_file_cache_type_t {
|
||||
SYS_FILE_CACHE_AUTO = 0, // NOLINT(readability-identifier-naming)
|
||||
SYS_FILE_CACHE_RANDOM_ACCESS = 1, // NOLINT(readability-identifier-naming)
|
||||
SYS_FILE_CACHE_SEQUENTIAL_SCAN = 2 // NOLINT(readability-identifier-naming)
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct sys_file_find_t {
|
||||
std::filesystem::path path_with_name;
|
||||
SysFileTimeStruct last_access_time;
|
||||
SysFileTimeStruct last_write_time;
|
||||
uint64_t size;
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct sys_dir_entry_t {
|
||||
std::string name;
|
||||
bool is_file;
|
||||
};
|
||||
|
||||
void SysFileRead(void* data, uint32_t size, sys_file_t& f,
|
||||
uint32_t* bytes_read = nullptr); // NOLINT(google-runtime-references)
|
||||
void SysFileWrite(const void* data, uint32_t size, sys_file_t& f,
|
||||
uint32_t* bytes_written = nullptr); // NOLINT(google-runtime-references)
|
||||
sys_file_t* SysFileCreate(const std::filesystem::path& file_name);
|
||||
sys_file_t* SysFileOpenR(const std::filesystem::path& file_name,
|
||||
sys_file_cache_type_t cache_type = SYS_FILE_CACHE_AUTO);
|
||||
sys_file_t* SysFileOpenW(const std::filesystem::path& file_name,
|
||||
sys_file_cache_type_t cache_type = SYS_FILE_CACHE_AUTO);
|
||||
sys_file_t* SysFileOpen(uint8_t* buf, uint32_t buf_size);
|
||||
sys_file_t* SysFileCreate();
|
||||
sys_file_t* SysFileOpenRw(const std::filesystem::path& file_name,
|
||||
sys_file_cache_type_t cache_type = SYS_FILE_CACHE_AUTO);
|
||||
void SysFileClose(sys_file_t* f);
|
||||
uint64_t SysFileSize(sys_file_t& f); // NOLINT(google-runtime-references)
|
||||
bool SysFileSeek(sys_file_t& f, uint64_t offset); // NOLINT(google-runtime-references)
|
||||
uint64_t SysFileTell(sys_file_t& f); // NOLINT(google-runtime-references)
|
||||
bool SysFileTruncate(sys_file_t& f, uint64_t size); // NOLINT(google-runtime-references)
|
||||
bool SysFileUnlink(sys_file_t& f,
|
||||
const std::filesystem::path& name); // NOLINT(google-runtime-references)
|
||||
void SysFileWrite(uint32_t n, sys_file_t& f); // NOLINT(google-runtime-references)
|
||||
uint64_t SysFileSize(const std::filesystem::path& file_name);
|
||||
bool SysFileIsError(sys_file_t& f); // NOLINT(google-runtime-references)
|
||||
bool SysFileIsDirectoryExisting(const std::filesystem::path& path);
|
||||
bool SysFileIsFileExisting(const std::filesystem::path& name);
|
||||
bool SysFileCreateDirectory(const std::filesystem::path& path);
|
||||
bool SysFileDeleteDirectory(const std::filesystem::path& path);
|
||||
bool SysFileDeleteFile(const std::filesystem::path& name);
|
||||
bool SysFileFlush(sys_file_t& f); // NOLINT(google-runtime-references)
|
||||
SysFileTimeStruct SysFileGetLastAccessTimeUtc(const std::filesystem::path& name);
|
||||
SysFileTimeStruct SysFileGetLastWriteTimeUtc(const std::filesystem::path& name);
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
void SysFileGetLastAccessAndWriteTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& a,
|
||||
SysFileTimeStruct& w);
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
void SysFileGetLastAccessAndWriteTimeUtc(sys_file_t& f, SysFileTimeStruct& a, SysFileTimeStruct& w);
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
bool SysFileSetLastAccessTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& access);
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
bool SysFileSetLastWriteTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& write);
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
|
||||
SysFileTimeStruct& access, SysFileTimeStruct& write);
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_find_t>& out);
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out);
|
||||
bool SysFileCopyFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
bool SysFileMoveFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
void SysFileRemoveReadonly(const std::filesystem::path& name);
|
||||
|
||||
#endif /* KYTY_COMMON_PLATFORM_SYSFILEIO_H_ */
|
||||
@@ -0,0 +1,123 @@
|
||||
#include "common/common.h"
|
||||
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_LINUX
|
||||
// #error "KYTY_PLATFORM != KYTY_PLATFORM_LINUX"
|
||||
#else
|
||||
|
||||
#include "common/platform/sysDbg.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <sys/param.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void SysStackWalk(void** /*stack*/, int* depth) {
|
||||
*depth = 0;
|
||||
}
|
||||
|
||||
void SysStackUsagePrint(sys_dbg_stack_info_t& stack) {
|
||||
printf("stack: (0x%" PRIx64 ", %" PRIu64 ")\n", static_cast<uint64_t>(stack.commited_addr),
|
||||
static_cast<uint64_t>(stack.commited_size));
|
||||
printf("code: (0x%" PRIx64 ", %" PRIu64 ")\n", static_cast<uint64_t>(stack.code_addr),
|
||||
static_cast<uint64_t>(stack.code_size));
|
||||
}
|
||||
|
||||
void SysStackUsage(sys_dbg_stack_info_t& s) {
|
||||
pid_t pid = getpid();
|
||||
|
||||
// printf("pid = %"I64"d\n", (int64_t)pid);
|
||||
|
||||
[[maybe_unused]] int result = 0;
|
||||
|
||||
char str[1024];
|
||||
char str2[1024];
|
||||
result = sprintf(str, "/proc/%d/exe", static_cast<int>(pid));
|
||||
|
||||
ssize_t buff_len = 0;
|
||||
if ((buff_len = readlink(str, str2, 1023)) == -1) {
|
||||
return;
|
||||
}
|
||||
str2[buff_len] = '\0';
|
||||
const char* name = basename(str2);
|
||||
|
||||
result = sprintf(str, "/proc/%d/maps", static_cast<int>(pid));
|
||||
|
||||
memset(&s, 0, sizeof(sys_dbg_stack_info_t));
|
||||
|
||||
FILE* f = fopen(str, "r");
|
||||
|
||||
if (f == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// printf("&str = %"I64"x\n", (uint64_t)&str);
|
||||
|
||||
uint64_t addr = 0;
|
||||
uint64_t endaddr = 0;
|
||||
[[maybe_unused]] uint64_t size = 0;
|
||||
uint64_t offset = 0;
|
||||
uint64_t inode = 0;
|
||||
char permissions[8] = {};
|
||||
char device[8] = {};
|
||||
char filename[MAXPATHLEN] = {};
|
||||
|
||||
auto check_addr = reinterpret_cast<uintptr_t>(&f);
|
||||
|
||||
while (true) {
|
||||
if (feof(f) != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (fgets(str, sizeof(str), f) == nullptr) {
|
||||
break;
|
||||
}
|
||||
|
||||
filename[0] = 0;
|
||||
permissions[0] = 0;
|
||||
addr = 0;
|
||||
size = 0;
|
||||
|
||||
// printf("%s", str);
|
||||
|
||||
// NOLINTNEXTLINE(cert-err34-c)
|
||||
result = sscanf(str, "%" SCNx64 "-%" SCNx64 " %s %" SCNx64 " %s %" SCNx64 " %s", &addr,
|
||||
&endaddr, permissions, &offset, device, &inode, filename);
|
||||
|
||||
size = endaddr - addr;
|
||||
|
||||
bool read = (strchr(permissions, 'r') != nullptr);
|
||||
bool write = (strchr(permissions, 'w') != nullptr);
|
||||
bool exec = (strchr(permissions, 'x') != nullptr);
|
||||
|
||||
// printf("%016"I64"x, %"I64"d, %s, %d, %d\n", addr, size, filename, read, write);
|
||||
|
||||
if (read && write && !exec && strncmp(filename, "[stack", 6) == 0) {
|
||||
// printf("stack: %016"I64"x, %"I64"d\n", addr, size);
|
||||
|
||||
if (check_addr >= addr && check_addr < addr + size) {
|
||||
s.addr = addr;
|
||||
s.total_size = size;
|
||||
s.commited_addr = addr;
|
||||
s.commited_size = size;
|
||||
|
||||
if (s.code_addr != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (read && !write && exec && strstr(filename, name) != nullptr) {
|
||||
s.code_addr = addr;
|
||||
s.code_size = size;
|
||||
|
||||
if (s.addr != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = fclose(f);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,557 @@
|
||||
#include "common/common.h"
|
||||
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_LINUX
|
||||
// #error "KYTY_PLATFORM != KYTY_PLATFORM_LINUX"
|
||||
#else
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/platform/sysFileIO.h"
|
||||
#include "common/platform/sysTimer.h"
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <utime.h>
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
enum sys_file_type_t {
|
||||
SYS_FILE_ERROR, // NOLINT(readability-identifier-naming)
|
||||
SYS_FILE_MEMORY_STAT, // NOLINT(readability-identifier-naming)
|
||||
SYS_FILE_FILE, // NOLINT(readability-identifier-naming)
|
||||
SYS_FILE_MEMORY_DYN // NOLINT(readability-identifier-naming)
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct sys_file_mem_buf_t {
|
||||
uint8_t* base;
|
||||
uint8_t* ptr;
|
||||
uint32_t size;
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct sys_file_t {
|
||||
sys_file_type_t type;
|
||||
union {
|
||||
FILE* f;
|
||||
sys_file_mem_buf_t* buf;
|
||||
};
|
||||
};
|
||||
|
||||
static std::filesystem::path get_internal_name(const std::filesystem::path& name) {
|
||||
return name.is_absolute() ? name : (std::filesystem::path(".") / name);
|
||||
}
|
||||
|
||||
void SysFileRead(void* data, uint32_t size, sys_file_t& f, uint32_t* bytes_read) {
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
size_t w = fread(data, 1, size, f.f);
|
||||
if (bytes_read != nullptr) {
|
||||
*bytes_read = w;
|
||||
}
|
||||
} else if (f.type == SYS_FILE_MEMORY_STAT) {
|
||||
uint32_t s = size;
|
||||
if (f.buf->size != 0) {
|
||||
uint32_t l = f.buf->size - (f.buf->ptr - f.buf->base);
|
||||
if (s > l) {
|
||||
s = l;
|
||||
}
|
||||
}
|
||||
memcpy(data, f.buf->ptr, s);
|
||||
f.buf->ptr += s;
|
||||
if (bytes_read != nullptr) {
|
||||
*bytes_read = s;
|
||||
}
|
||||
} else if (f.type == SYS_FILE_MEMORY_DYN) {
|
||||
uint32_t s = size;
|
||||
if (f.buf->size != 0) {
|
||||
uint32_t l = f.buf->size - (f.buf->ptr - f.buf->base);
|
||||
if (s > l) {
|
||||
s = l;
|
||||
}
|
||||
} else {
|
||||
s = 0;
|
||||
}
|
||||
memcpy(data, f.buf->ptr, s);
|
||||
f.buf->ptr += s;
|
||||
if (bytes_read != nullptr) {
|
||||
*bytes_read = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SysFileWrite(const void* data, uint32_t size, sys_file_t& f, uint32_t* bytes_written) {
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
size_t w = fwrite(data, 1, size, f.f);
|
||||
if (bytes_written != nullptr) {
|
||||
*bytes_written = w;
|
||||
}
|
||||
} else if (f.type == SYS_FILE_MEMORY_STAT) {
|
||||
uint32_t s = size;
|
||||
if (f.buf->size != 0) {
|
||||
uint32_t l = f.buf->size - (f.buf->ptr - f.buf->base);
|
||||
if (s > l) {
|
||||
s = l;
|
||||
}
|
||||
}
|
||||
memcpy(f.buf->ptr, data, s);
|
||||
f.buf->ptr += s;
|
||||
if (bytes_written != nullptr) {
|
||||
*bytes_written = s;
|
||||
}
|
||||
} else if (f.type == SYS_FILE_MEMORY_DYN) {
|
||||
uint32_t pos = f.buf->ptr - f.buf->base;
|
||||
if (f.buf->size < pos + size) {
|
||||
f.buf->base = static_cast<uint8_t*>(std::realloc(f.buf->base, pos + size));
|
||||
f.buf->ptr = f.buf->base + pos;
|
||||
f.buf->size = pos + size;
|
||||
}
|
||||
memcpy(f.buf->ptr, data, size);
|
||||
f.buf->ptr += size;
|
||||
if (bytes_written != nullptr) {
|
||||
*bytes_written = size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SysFileWrite(uint32_t n, sys_file_t& f) {
|
||||
SysFileWrite(&n, 4, f);
|
||||
}
|
||||
|
||||
sys_file_t* SysFileCreate(const std::filesystem::path& file_name) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
auto real_name = get_internal_name(file_name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
ret->f = fopen(real_name_str.c_str(), "w+");
|
||||
|
||||
if (ret->f == nullptr) {
|
||||
printf("can't create file: %s\n", real_name_str.c_str());
|
||||
}
|
||||
|
||||
ret->type = SYS_FILE_FILE;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpenR(const std::filesystem::path& file_name,
|
||||
sys_file_cache_type_t /*cache_type*/) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
ret->type = SYS_FILE_FILE;
|
||||
|
||||
auto internal_name = get_internal_name(file_name);
|
||||
auto internal_name_str = internal_name.string();
|
||||
|
||||
FILE* f = fopen(internal_name_str.c_str(), "r");
|
||||
|
||||
if (f == nullptr) {
|
||||
ret->type = SYS_FILE_ERROR;
|
||||
}
|
||||
|
||||
ret->f = f;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpen(uint8_t* buf, uint32_t buf_size) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
ret->type = SYS_FILE_MEMORY_STAT;
|
||||
ret->buf = new sys_file_mem_buf_t;
|
||||
ret->buf->base = buf;
|
||||
ret->buf->ptr = buf;
|
||||
ret->buf->size = buf_size;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileCreate() {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
ret->type = SYS_FILE_MEMORY_DYN;
|
||||
ret->buf = new sys_file_mem_buf_t;
|
||||
ret->buf->base = nullptr;
|
||||
ret->buf->ptr = nullptr;
|
||||
ret->buf->size = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpenW(const std::filesystem::path& file_name,
|
||||
sys_file_cache_type_t /*cache_type*/) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
auto real_name = get_internal_name(file_name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
FILE* f = fopen(real_name_str.c_str(), "r+");
|
||||
|
||||
if (f == nullptr) {
|
||||
ret->type = SYS_FILE_ERROR;
|
||||
} else {
|
||||
ret->type = SYS_FILE_FILE;
|
||||
}
|
||||
|
||||
ret->f = f;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpenRw(const std::filesystem::path& file_name,
|
||||
sys_file_cache_type_t /*cache_type*/) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
auto real_name = get_internal_name(file_name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
FILE* f = fopen(real_name_str.c_str(), "r+");
|
||||
|
||||
if (f == nullptr) {
|
||||
ret->type = SYS_FILE_ERROR;
|
||||
} else {
|
||||
ret->type = SYS_FILE_FILE;
|
||||
}
|
||||
|
||||
ret->f = f;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SysFileClose(sys_file_t* f) {
|
||||
[[maybe_unused]] int result = 0;
|
||||
|
||||
if (f->type == SYS_FILE_FILE && f->f != nullptr) {
|
||||
result = fclose(f->f);
|
||||
} else if (f->type == SYS_FILE_MEMORY_STAT) {
|
||||
delete f->buf;
|
||||
} else if (f->type == SYS_FILE_MEMORY_DYN) {
|
||||
std::free(f->buf->base);
|
||||
delete f->buf;
|
||||
}
|
||||
|
||||
// f.type = SYS_FILE_ERROR;
|
||||
delete f;
|
||||
}
|
||||
|
||||
uint64_t SysFileSize(sys_file_t& f) {
|
||||
[[maybe_unused]] int result = 0;
|
||||
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
uint32_t pos = ftell(f.f);
|
||||
result = fseek(f.f, 0, SEEK_END);
|
||||
uint32_t size = ftell(f.f);
|
||||
result = fseek(f.f, pos, SEEK_SET);
|
||||
return size;
|
||||
}
|
||||
|
||||
if (f.type == SYS_FILE_MEMORY_STAT || f.type == SYS_FILE_MEMORY_DYN) {
|
||||
return f.buf->size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t SysFileSize(const std::filesystem::path& file_name) {
|
||||
sys_file_t* f = SysFileOpenR(file_name);
|
||||
uint64_t size = SysFileSize(*f);
|
||||
SysFileClose(f);
|
||||
return size;
|
||||
}
|
||||
|
||||
bool SysFileTruncate(sys_file_t& /*f*/, uint64_t /*size*/) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SysFileUnlink(sys_file_t& /*f*/, const std::filesystem::path& name) {
|
||||
return SysFileDeleteFile(name);
|
||||
}
|
||||
|
||||
bool SysFileSeek(sys_file_t& f, uint64_t offset) {
|
||||
bool ok = true;
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
ok = (fseek(f.f, static_cast<int64_t>(offset), SEEK_SET) == 0);
|
||||
// LARGE_INTEGER s;
|
||||
// s.QuadPart = offset;
|
||||
// SetFilePointerEx(f.handle, s, 0, FILE_BEGIN);
|
||||
// printf("seek: %u\n", offset);
|
||||
} else if (f.type == SYS_FILE_MEMORY_STAT || f.type == SYS_FILE_MEMORY_DYN) {
|
||||
f.buf->ptr = f.buf->base + offset;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
uint64_t SysFileTell(sys_file_t& f) {
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
return ftell(f.f);
|
||||
}
|
||||
|
||||
if (f.type == SYS_FILE_MEMORY_STAT || f.type == SYS_FILE_MEMORY_DYN) {
|
||||
return f.buf->ptr - f.buf->base;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool SysFileIsError(sys_file_t& f) {
|
||||
return f.type == SYS_FILE_ERROR || (f.type == SYS_FILE_FILE && f.f == nullptr);
|
||||
}
|
||||
|
||||
bool SysFileIsDirectoryExisting(const std::filesystem::path& path) {
|
||||
auto real_name = get_internal_name(path);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
struct stat s {};
|
||||
|
||||
if (0 != stat(real_name_str.c_str(), &s)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return S_ISDIR(s.st_mode); // NOLINT
|
||||
}
|
||||
|
||||
bool SysFileIsFileExisting(const std::filesystem::path& name) {
|
||||
auto real_name = get_internal_name(name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
struct stat s {};
|
||||
|
||||
if (0 != stat(real_name_str.c_str(), &s)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !S_ISDIR(s.st_mode); // NOLINT
|
||||
}
|
||||
|
||||
bool SysFileCreateDirectory(const std::filesystem::path& path) {
|
||||
auto real_name = get_internal_name(path);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
mode_t m = S_IRWXU | S_IRWXG | S_IRWXO; // NOLINT
|
||||
|
||||
int r = mkdir(real_name_str.c_str(), m);
|
||||
|
||||
if (r != 0) {
|
||||
int e = errno;
|
||||
if (e == EEXIST) {
|
||||
printf("mkdir(%s, %" PRIx32 ") failed: The named file exists\n", real_name_str.c_str(),
|
||||
static_cast<uint32_t>(m));
|
||||
} else {
|
||||
printf("mkdir(%s, %" PRIx32 ") failed: %d\n", real_name_str.c_str(),
|
||||
static_cast<uint32_t>(m), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SysFileDeleteDirectory(const std::filesystem::path& path) {
|
||||
auto real_name = get_internal_name(path);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
return 0 == remove(real_name_str.c_str());
|
||||
}
|
||||
|
||||
bool SysFileDeleteFile(const std::filesystem::path& name) {
|
||||
auto real_name = get_internal_name(name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
return 0 == unlink(real_name_str.c_str());
|
||||
}
|
||||
|
||||
bool SysFileFlush(sys_file_t& f) {
|
||||
if (f.type == SYS_FILE_FILE && f.f != nullptr) {
|
||||
return (fflush(f.f) == 0);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
SysFileTimeStruct SysFileGetLastAccessTimeUtc(const std::filesystem::path& name) {
|
||||
SysFileTimeStruct r {};
|
||||
|
||||
auto real_name = get_internal_name(name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
struct stat s {};
|
||||
|
||||
if (0 != stat(real_name_str.c_str(), &s)) {
|
||||
r.is_invalid = true;
|
||||
} else {
|
||||
r.is_invalid = false;
|
||||
r.time = s.st_atime;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
SysFileTimeStruct SysFileGetLastWriteTimeUtc(const std::filesystem::path& name) {
|
||||
SysFileTimeStruct r {};
|
||||
|
||||
auto real_name = get_internal_name(name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
struct stat s {};
|
||||
|
||||
if (0 != stat(real_name_str.c_str(), &s)) {
|
||||
r.is_invalid = true;
|
||||
} else {
|
||||
r.is_invalid = false;
|
||||
r.time = s.st_mtime;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void SysFileGetLastAccessAndWriteTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& a,
|
||||
SysFileTimeStruct& w) {
|
||||
auto real_name = get_internal_name(name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
struct stat s {};
|
||||
|
||||
if (0 != stat(real_name_str.c_str(), &s)) {
|
||||
a.is_invalid = true;
|
||||
w.is_invalid = true;
|
||||
} else {
|
||||
a.is_invalid = false;
|
||||
w.is_invalid = false;
|
||||
a.time = s.st_atime;
|
||||
w.time = s.st_mtime;
|
||||
}
|
||||
}
|
||||
|
||||
void SysFileGetLastAccessAndWriteTimeUtc(sys_file_t& /*f*/, SysFileTimeStruct& /*a*/,
|
||||
SysFileTimeStruct& /*w*/) {
|
||||
EXIT("not implemented\n");
|
||||
}
|
||||
|
||||
bool SysFileSetLastAccessTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& access) {
|
||||
if (access.is_invalid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto real_name = get_internal_name(name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
struct stat s {};
|
||||
|
||||
if (0 != stat(real_name_str.c_str(), &s)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
utimbuf times {};
|
||||
times.actime = access.time;
|
||||
times.modtime = s.st_mtime;
|
||||
|
||||
return !(0 != utime(real_name_str.c_str(), ×));
|
||||
|
||||
// if (0 != stat(n.data(), &s))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// times.actime += times.actime - s.st_atime;
|
||||
// times.modtime += times.modtime - s.st_mtime;
|
||||
//
|
||||
// if (0 != utime(n.data(), ×))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
}
|
||||
|
||||
bool SysFileSetLastWriteTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& write) {
|
||||
if (write.is_invalid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto real_name = get_internal_name(name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
struct stat s {};
|
||||
|
||||
if (0 != stat(real_name_str.c_str(), &s)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
utimbuf times {};
|
||||
times.actime = s.st_atime;
|
||||
times.modtime = write.time;
|
||||
|
||||
return !(0 != utime(real_name_str.c_str(), ×));
|
||||
|
||||
// if (0 != stat(n.data(), &s))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// times.actime += times.actime - s.st_atime;
|
||||
// times.modtime += times.modtime - s.st_mtime;
|
||||
//
|
||||
// if (0 != utime(n.data(), ×))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
}
|
||||
|
||||
bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
|
||||
SysFileTimeStruct& access, SysFileTimeStruct& write) {
|
||||
if (access.is_invalid || write.is_invalid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto real_name = get_internal_name(name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
struct stat s {};
|
||||
|
||||
if (0 != stat(real_name_str.c_str(), &s)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
utimbuf times {};
|
||||
times.actime = access.time;
|
||||
times.modtime = write.time;
|
||||
|
||||
return !(0 != utime(real_name_str.c_str(), ×));
|
||||
|
||||
// if (0 != stat(n.data(), &s))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// times.actime += times.actime - s.st_atime;
|
||||
// times.modtime += times.modtime - s.st_mtime;
|
||||
//
|
||||
// if (0 != utime(n.data(), ×))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
}
|
||||
|
||||
void SysFileFindFiles(const std::filesystem::path& /*path*/,
|
||||
std::vector<sys_file_find_t>& /*out*/) {
|
||||
EXIT("not implemented\n");
|
||||
}
|
||||
|
||||
void SysFileGetDents(const std::filesystem::path& /*path*/, std::vector<sys_dir_entry_t>& /*out*/) {
|
||||
EXIT("not implemented\n");
|
||||
}
|
||||
|
||||
bool SysFileCopyFile(const std::filesystem::path& /*src*/, const std::filesystem::path& /*dst*/) {
|
||||
EXIT("not implemented\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SysFileMoveFile(const std::filesystem::path& /*src*/, const std::filesystem::path& /*dst*/) {
|
||||
EXIT("not implemented\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
void SysFileRemoveReadonly(const std::filesystem::path& /*name*/) {
|
||||
EXIT("not implemented\n");
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,451 @@
|
||||
#include "common/common.h"
|
||||
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_LINUX
|
||||
// #error "KYTY_PLATFORM != KYTY_PLATFORM_LINUX"
|
||||
#else
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/platform/sysVirtual.h"
|
||||
#include "common/virtualMemory.h"
|
||||
|
||||
#include <map>
|
||||
#include <pthread.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
// IWYU pragma: no_include <asm/mman-common.h>
|
||||
// IWYU pragma: no_include <asm/mman.h>
|
||||
// IWYU pragma: no_include <bits/pthread_types.h>
|
||||
// IWYU pragma: no_include <linux/mman.h>
|
||||
|
||||
#if defined(MAP_FIXED_NOREPLACE) && KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
#define KYTY_FIXED_NOREPLACE
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
static pthread_mutex_t g_virtual_mutex {};
|
||||
static std::map<uintptr_t, size_t>* g_allocs = nullptr;
|
||||
static std::map<uintptr_t, int>* g_protects = nullptr;
|
||||
|
||||
void SysVirtualInit() {
|
||||
pthread_mutexattr_t attr {};
|
||||
|
||||
pthread_mutexattr_init(&attr);
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_FAST_NP);
|
||||
#else
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
|
||||
#endif
|
||||
pthread_mutex_init(&g_virtual_mutex, &attr);
|
||||
pthread_mutexattr_destroy(&attr);
|
||||
|
||||
g_allocs = new std::map<uintptr_t, size_t>;
|
||||
g_protects = new std::map<uintptr_t, int>;
|
||||
}
|
||||
|
||||
static int get_protection_flag(VirtualMemory::Mode mode) {
|
||||
int protect = PROT_NONE;
|
||||
switch (mode) {
|
||||
case VirtualMemory::Mode::Read: protect = PROT_READ; break;
|
||||
case VirtualMemory::Mode::Write:
|
||||
case VirtualMemory::Mode::ReadWrite: protect = PROT_READ | PROT_WRITE; break; // NOLINT
|
||||
case VirtualMemory::Mode::Execute: protect = PROT_EXEC; break;
|
||||
case VirtualMemory::Mode::ExecuteRead: protect = PROT_EXEC | PROT_READ; break; // NOLINT
|
||||
case VirtualMemory::Mode::ExecuteWrite:
|
||||
case VirtualMemory::Mode::ExecuteReadWrite:
|
||||
protect = PROT_EXEC | PROT_WRITE | PROT_READ;
|
||||
break; // NOLINT
|
||||
case VirtualMemory::Mode::NoAccess:
|
||||
default: protect = PROT_NONE; break;
|
||||
}
|
||||
return protect;
|
||||
}
|
||||
|
||||
static VirtualMemory::Mode get_protection_flag(int mode) {
|
||||
switch (mode) {
|
||||
case PROT_NONE: return VirtualMemory::Mode::NoAccess;
|
||||
case PROT_READ: return VirtualMemory::Mode::Read;
|
||||
case PROT_WRITE: return VirtualMemory::Mode::Write;
|
||||
case PROT_READ | PROT_WRITE: return VirtualMemory::Mode::ReadWrite; // NOLINT
|
||||
case PROT_EXEC: return VirtualMemory::Mode::Execute;
|
||||
case PROT_EXEC | PROT_WRITE: return VirtualMemory::Mode::ExecuteWrite; // NOLINT
|
||||
case PROT_EXEC | PROT_READ: return VirtualMemory::Mode::ExecuteRead; // NOLINT
|
||||
case PROT_EXEC | PROT_WRITE | PROT_READ:
|
||||
return VirtualMemory::Mode::ExecuteReadWrite; // NOLINT
|
||||
default: return VirtualMemory::Mode::NoAccess;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
|
||||
auto addr = static_cast<uintptr_t>(address);
|
||||
|
||||
int protect = get_protection_flag(mode);
|
||||
|
||||
void* ptr =
|
||||
mmap(reinterpret_cast<void*>(addr), size, protect, MAP_PRIVATE | MAP_ANON, -1, 0); // NOLINT
|
||||
|
||||
auto ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
|
||||
if (ptr != MAP_FAILED) {
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
(*g_allocs)[ret_addr] = size;
|
||||
uintptr_t page_start = ret_addr >> 12u;
|
||||
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
|
||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
||||
(*g_protects)[page] = protect;
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
}
|
||||
|
||||
return ret_addr;
|
||||
}
|
||||
|
||||
static uintptr_t align_up(uintptr_t addr, uint64_t alignment) {
|
||||
return (addr + alignment - 1) & ~(alignment - 1);
|
||||
}
|
||||
|
||||
uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
|
||||
uint64_t alignment) {
|
||||
if (alignment == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
|
||||
auto addr = static_cast<uintptr_t>(address);
|
||||
int protect = get_protection_flag(mode);
|
||||
|
||||
void* ptr =
|
||||
mmap(reinterpret_cast<void*>(addr), size, protect, MAP_PRIVATE | MAP_ANON, -1, 0); // NOLINT
|
||||
|
||||
auto ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
|
||||
if (ptr != MAP_FAILED && ((ret_addr & (alignment - 1)) != 0)) {
|
||||
munmap(ptr, size);
|
||||
|
||||
ptr = mmap(reinterpret_cast<void*>(addr), size + alignment, protect,
|
||||
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); // NOLINT
|
||||
ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
if (ptr != MAP_FAILED) {
|
||||
munmap(ptr, size + alignment);
|
||||
auto aligned_addr = align_up(ret_addr, alignment);
|
||||
#ifdef KYTY_FIXED_NOREPLACE
|
||||
// NOLINTNEXTLINE
|
||||
ptr = mmap(reinterpret_cast<void*>(aligned_addr), size, protect,
|
||||
MAP_FIXED_NOREPLACE | MAP_PRIVATE | MAP_ANON, -1, 0);
|
||||
#else
|
||||
// NOLINTNEXTLINE
|
||||
ptr = mmap(reinterpret_cast<void*>(aligned_addr), size, protect,
|
||||
MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0);
|
||||
#endif
|
||||
ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
if (ptr != MAP_FAILED && ((ret_addr & (alignment - 1)) != 0)) {
|
||||
munmap(ptr, size);
|
||||
ret_addr = 0;
|
||||
ptr = MAP_FAILED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ptr == MAP_FAILED) {
|
||||
return SysVirtualAllocAligned(address, size, mode, alignment << 1u);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
(*g_allocs)[ret_addr] = size;
|
||||
uintptr_t page_start = ret_addr >> 12u;
|
||||
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
|
||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
||||
(*g_protects)[page] = protect;
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
|
||||
return ret_addr;
|
||||
}
|
||||
|
||||
static bool is_mapped(void* ptr, size_t length) {
|
||||
FILE* file = fopen("/proc/self/maps", "r");
|
||||
char line[1024];
|
||||
bool ret = false;
|
||||
auto addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
|
||||
while (feof(file) == 0) {
|
||||
if (fgets(line, 1024, file) == nullptr) {
|
||||
break;
|
||||
}
|
||||
uint64_t start = 0;
|
||||
uint64_t end = 0;
|
||||
// NOLINTNEXTLINE(cert-err34-c)
|
||||
if (sscanf(line, "%" SCNx64 "-%" SCNx64, &start, &end) != 2) {
|
||||
continue;
|
||||
}
|
||||
if (addr >= start && addr + length <= end) {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(file);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
|
||||
auto addr = static_cast<uintptr_t>(address);
|
||||
int protect = get_protection_flag(mode);
|
||||
|
||||
#ifdef KYTY_FIXED_NOREPLACE
|
||||
// NOLINTNEXTLINE
|
||||
void* ptr = mmap(reinterpret_cast<void*>(addr), size, protect,
|
||||
MAP_FIXED_NOREPLACE | MAP_PRIVATE | MAP_ANON, -1, 0);
|
||||
#else
|
||||
// NOLINTNEXTLINE
|
||||
void* ptr = (is_mapped(reinterpret_cast<void*>(addr), size)
|
||||
? MAP_FAILED
|
||||
: mmap(reinterpret_cast<void*>(addr), size, protect,
|
||||
MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0));
|
||||
#endif
|
||||
|
||||
auto ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
|
||||
if (ptr != MAP_FAILED && ret_addr != addr) {
|
||||
munmap(ptr, size);
|
||||
ret_addr = 0;
|
||||
ptr = MAP_FAILED;
|
||||
}
|
||||
|
||||
if (ptr != MAP_FAILED) {
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
(*g_allocs)[ret_addr] = size;
|
||||
uintptr_t page_start = ret_addr >> 12u;
|
||||
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
|
||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
||||
(*g_protects)[page] = protect;
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SysVirtualCommit(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
return SysVirtualProtect(address, size, mode);
|
||||
}
|
||||
|
||||
uint64_t SysVirtualReserve(uint64_t address, uint64_t size) {
|
||||
return SysVirtualReserveAligned(address, size, 1);
|
||||
}
|
||||
|
||||
uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alignment) {
|
||||
if (alignment == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
|
||||
auto addr = static_cast<uintptr_t>(address);
|
||||
|
||||
void* ptr = mmap(reinterpret_cast<void*>(addr), size, PROT_NONE,
|
||||
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); // NOLINT
|
||||
|
||||
auto ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
|
||||
if (ptr != MAP_FAILED && ((ret_addr & (alignment - 1)) != 0)) {
|
||||
munmap(ptr, size);
|
||||
|
||||
ptr = mmap(reinterpret_cast<void*>(addr), size + alignment, PROT_NONE,
|
||||
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); // NOLINT
|
||||
ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
if (ptr != MAP_FAILED) {
|
||||
munmap(ptr, size + alignment);
|
||||
auto aligned_addr = align_up(ret_addr, alignment);
|
||||
#ifdef KYTY_FIXED_NOREPLACE
|
||||
// NOLINTNEXTLINE
|
||||
ptr = mmap(reinterpret_cast<void*>(aligned_addr), size, PROT_NONE,
|
||||
MAP_FIXED_NOREPLACE | MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0);
|
||||
#else
|
||||
// NOLINTNEXTLINE
|
||||
ptr = mmap(reinterpret_cast<void*>(aligned_addr), size, PROT_NONE,
|
||||
MAP_FIXED | MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0);
|
||||
#endif
|
||||
ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
if (ptr != MAP_FAILED && ((ret_addr & (alignment - 1)) != 0)) {
|
||||
munmap(ptr, size);
|
||||
ret_addr = 0;
|
||||
ptr = MAP_FAILED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ptr == MAP_FAILED) {
|
||||
return SysVirtualReserveAligned(address, size, alignment << 1u);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
(*g_allocs)[ret_addr] = size;
|
||||
uintptr_t page_start = ret_addr >> 12u;
|
||||
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
|
||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
||||
(*g_protects)[page] = PROT_NONE;
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
|
||||
return ret_addr;
|
||||
}
|
||||
|
||||
bool SysVirtualReserveFixed(uint64_t address, uint64_t size) {
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
|
||||
auto addr = static_cast<uintptr_t>(address);
|
||||
|
||||
#ifdef KYTY_FIXED_NOREPLACE
|
||||
// NOLINTNEXTLINE
|
||||
void* ptr = mmap(reinterpret_cast<void*>(addr), size, PROT_NONE,
|
||||
MAP_FIXED_NOREPLACE | MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0);
|
||||
#else
|
||||
// NOLINTNEXTLINE
|
||||
void* ptr = (is_mapped(reinterpret_cast<void*>(addr), size)
|
||||
? MAP_FAILED
|
||||
: mmap(reinterpret_cast<void*>(addr), size, PROT_NONE,
|
||||
MAP_FIXED | MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0));
|
||||
#endif
|
||||
|
||||
auto ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
|
||||
if (ptr != MAP_FAILED && ret_addr != addr) {
|
||||
munmap(ptr, size);
|
||||
ret_addr = 0;
|
||||
ptr = MAP_FAILED;
|
||||
}
|
||||
|
||||
if (ptr != MAP_FAILED) {
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
(*g_allocs)[ret_addr] = size;
|
||||
uintptr_t page_start = ret_addr >> 12u;
|
||||
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
|
||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
||||
(*g_protects)[page] = PROT_NONE;
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SysVirtualDecommit(uint64_t address, uint64_t size) {
|
||||
return SysVirtualProtect(address, size, VirtualMemory::Mode::NoAccess);
|
||||
}
|
||||
|
||||
bool SysVirtualFree(uint64_t address) {
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
size_t size = 0;
|
||||
|
||||
auto addr = static_cast<uintptr_t>(address & ~static_cast<uint64_t>(0xfffu));
|
||||
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
if (auto s = g_allocs->find(addr); s != g_allocs->end()) {
|
||||
size = s->second;
|
||||
g_allocs->erase(s);
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
|
||||
if (size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (munmap(reinterpret_cast<void*>(addr), size) == 0) {
|
||||
uintptr_t page_start = addr >> 12u;
|
||||
uintptr_t page_end = (addr + size - 1) >> 12u;
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
||||
g_protects->erase(page);
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SysVirtualFreeRange(uint64_t address, uint64_t size) {
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
if (size == 0 || (address & 0xfffu) != 0 || (size & 0xfffu) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto addr = static_cast<uintptr_t>(address);
|
||||
const auto end = addr + size;
|
||||
if (end < addr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
auto next = g_allocs->upper_bound(addr);
|
||||
if (next == g_allocs->begin()) {
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
return false;
|
||||
}
|
||||
auto it = std::prev(next);
|
||||
const auto alloc_addr = it->first;
|
||||
const auto alloc_end = alloc_addr + it->second;
|
||||
if (addr < alloc_addr || end > alloc_end || munmap(reinterpret_cast<void*>(addr), size) != 0) {
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
g_allocs->erase(it);
|
||||
if (alloc_addr < addr) {
|
||||
(*g_allocs)[alloc_addr] = addr - alloc_addr;
|
||||
}
|
||||
if (end < alloc_end) {
|
||||
(*g_allocs)[end] = alloc_end - end;
|
||||
}
|
||||
for (uintptr_t page = addr >> 12u; page <= (end - 1u) >> 12u; page++) {
|
||||
g_protects->erase(page);
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SysVirtualProtect(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
|
||||
VirtualMemory::Mode* old_mode) {
|
||||
auto addr = static_cast<uintptr_t>(address);
|
||||
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
if (old_mode != nullptr) {
|
||||
if (auto s = g_protects->find(addr >> 12u); s != g_protects->end()) {
|
||||
*old_mode = get_protection_flag(s->second);
|
||||
} else {
|
||||
*old_mode = VirtualMemory::Mode::NoAccess;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
|
||||
uintptr_t page_start = addr >> 12u;
|
||||
uintptr_t page_end = (addr + size - 1) >> 12u;
|
||||
if (mprotect(reinterpret_cast<void*>(page_start << 12u), (page_end - page_start + 1) << 12u,
|
||||
get_protection_flag(mode)) == 0) {
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
||||
(*g_protects)[page] = get_protection_flag(mode);
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SysVirtualFlushInstructionCache(uint64_t /*address*/, uint64_t /*size*/) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,53 @@
|
||||
#ifndef KYTY_COMMON_PLATFORM_SYSSWAPBYTEORDER_H_
|
||||
#define KYTY_COMMON_PLATFORM_SYSSWAPBYTEORDER_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
inline uint16_t SwapByteOrder16(uint16_t value) {
|
||||
uint16_t hi = value << 8u;
|
||||
uint16_t lo = value >> 8u;
|
||||
return hi | lo;
|
||||
}
|
||||
|
||||
inline uint32_t SwapByteOrder32(uint32_t value) {
|
||||
return __builtin_bswap32(value);
|
||||
}
|
||||
|
||||
inline uint64_t SwapByteOrder64(uint64_t value) {
|
||||
return __builtin_bswap64(value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void SwapByteOrder(T& x) {
|
||||
if (sizeof(x) == 2) {
|
||||
if (std::is_signed_v<T>) {
|
||||
x = static_cast<std::make_signed_t<T>>(
|
||||
SwapByteOrder16(static_cast<std::make_unsigned_t<uint16_t>>(x)));
|
||||
} else {
|
||||
x = SwapByteOrder16(x);
|
||||
}
|
||||
}
|
||||
if (sizeof(x) == 4) {
|
||||
if (std::is_signed_v<T>) {
|
||||
x = static_cast<std::make_signed_t<T>>(
|
||||
SwapByteOrder32(static_cast<std::make_unsigned_t<uint32_t>>(x)));
|
||||
} else {
|
||||
x = SwapByteOrder32(x);
|
||||
}
|
||||
}
|
||||
if (sizeof(x) == 8) {
|
||||
if (std::is_signed_v<T>) {
|
||||
x = static_cast<std::make_signed_t<T>>(
|
||||
SwapByteOrder64(static_cast<std::make_unsigned_t<uint64_t>>(x)));
|
||||
} else {
|
||||
x = SwapByteOrder64(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void NoSwapByteOrder(T& x) {}
|
||||
|
||||
#endif /* KYTY_COMMON_PLATFORM_SYSSWAPBYTEORDER_H_ */
|
||||
@@ -0,0 +1,221 @@
|
||||
#ifndef KYTY_COMMON_PLATFORM_SYSTIMER_H_
|
||||
#define KYTY_COMMON_PLATFORM_SYSTIMER_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
#include <ctime>
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
struct SysTimeStruct {
|
||||
uint16_t Year; // NOLINT(readability-identifier-naming)
|
||||
uint16_t Month; // NOLINT(readability-identifier-naming)
|
||||
uint16_t Day; // NOLINT(readability-identifier-naming)
|
||||
uint16_t Hour; // NOLINT(readability-identifier-naming)
|
||||
uint16_t Minute; // NOLINT(readability-identifier-naming)
|
||||
uint16_t Second; // NOLINT(readability-identifier-naming)
|
||||
uint16_t Milliseconds; // NOLINT(readability-identifier-naming)
|
||||
bool is_invalid; // NOLINT(readability-identifier-naming)
|
||||
};
|
||||
|
||||
struct SysFileTimeStruct {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
FILETIME time;
|
||||
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
time_t time;
|
||||
#endif
|
||||
bool is_invalid;
|
||||
};
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
inline void SysFileToSystemTimeUtc(const SysFileTimeStruct& f, SysTimeStruct& t) {
|
||||
SYSTEMTIME s;
|
||||
|
||||
if (f.is_invalid || (FileTimeToSystemTime(&f.time, &s) == 0)) {
|
||||
t.is_invalid = true;
|
||||
return;
|
||||
}
|
||||
|
||||
t.is_invalid = false;
|
||||
t.Year = s.wYear;
|
||||
t.Month = s.wMonth;
|
||||
t.Day = s.wDay;
|
||||
t.Hour = s.wHour;
|
||||
t.Minute = s.wMinute;
|
||||
t.Second = (s.wSecond == 60 ? 59 : s.wSecond);
|
||||
t.Milliseconds = s.wMilliseconds;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
inline void SysTimeTToSystem(time_t t, SysTimeStruct& s) {
|
||||
SysFileTimeStruct ft {};
|
||||
LONGLONG ll = Int32x32To64(t, 10000000) + 116444736000000000;
|
||||
ft.time.dwLowDateTime = static_cast<DWORD>(ll);
|
||||
ft.time.dwHighDateTime = static_cast<DWORD>(static_cast<uint64_t>(ll) >> 32u);
|
||||
ft.is_invalid = false;
|
||||
SysFileToSystemTimeUtc(ft, s);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
inline void SysSystemToFileTimeUtc(const SysTimeStruct& f, SysFileTimeStruct& t) {
|
||||
SYSTEMTIME s;
|
||||
|
||||
s.wYear = f.Year;
|
||||
s.wMonth = f.Month;
|
||||
s.wDay = f.Day;
|
||||
s.wHour = f.Hour;
|
||||
s.wMinute = f.Minute;
|
||||
s.wSecond = f.Second;
|
||||
s.wMilliseconds = f.Milliseconds;
|
||||
|
||||
t.is_invalid = (f.is_invalid || (SystemTimeToFileTime(&s, &t.time) == 0));
|
||||
}
|
||||
|
||||
// Retrieves the current local date and time.
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
inline void SysGetSystemTime(SysTimeStruct& t) {
|
||||
SYSTEMTIME s;
|
||||
GetLocalTime(&s);
|
||||
|
||||
t.is_invalid = false;
|
||||
t.Year = s.wYear;
|
||||
t.Month = s.wMonth;
|
||||
t.Day = s.wDay;
|
||||
t.Hour = s.wHour;
|
||||
t.Minute = s.wMinute;
|
||||
t.Second = (s.wSecond == 60 ? 59 : s.wSecond);
|
||||
t.Milliseconds = s.wMilliseconds;
|
||||
}
|
||||
|
||||
// Retrieves the current system date and time in Coordinated Universal Time (UTC).
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
inline void SysGetSystemTimeUtc(SysTimeStruct& t) {
|
||||
SYSTEMTIME s;
|
||||
GetSystemTime(&s);
|
||||
|
||||
t.is_invalid = false;
|
||||
t.Year = s.wYear;
|
||||
t.Month = s.wMonth;
|
||||
t.Day = s.wDay;
|
||||
t.Hour = s.wHour;
|
||||
t.Minute = s.wMinute;
|
||||
t.Second = (s.wSecond == 60 ? 59 : s.wSecond);
|
||||
t.Milliseconds = s.wMilliseconds;
|
||||
}
|
||||
|
||||
inline void SysQueryPerformanceFrequency(uint64_t* freq) {
|
||||
LARGE_INTEGER f;
|
||||
QueryPerformanceFrequency(&f);
|
||||
*freq = f.QuadPart;
|
||||
}
|
||||
|
||||
inline void SysQueryPerformanceCounter(uint64_t* counter) {
|
||||
LARGE_INTEGER c;
|
||||
QueryPerformanceCounter(&c);
|
||||
*counter = c.QuadPart;
|
||||
}
|
||||
|
||||
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
|
||||
inline void SysFileToSystemTimeUtc(const SysFileTimeStruct& f, SysTimeStruct& t) {
|
||||
struct tm i {};
|
||||
|
||||
if (f.is_invalid || gmtime_r(&f.time, &i) == nullptr) {
|
||||
t.is_invalid = true;
|
||||
return;
|
||||
}
|
||||
|
||||
t.is_invalid = false;
|
||||
t.Year = i.tm_year + 1900;
|
||||
t.Month = i.tm_mon + 1;
|
||||
t.Day = i.tm_mday;
|
||||
t.Hour = i.tm_hour;
|
||||
t.Minute = i.tm_min;
|
||||
t.Second = (i.tm_sec == 60 ? 59 : i.tm_sec);
|
||||
t.Milliseconds = 0;
|
||||
}
|
||||
|
||||
inline void SysTimeTToSystem(time_t t, SysTimeStruct& s) {
|
||||
SysFileTimeStruct ft {};
|
||||
ft.time = t;
|
||||
ft.is_invalid = false;
|
||||
SysFileToSystemTimeUtc(ft, s);
|
||||
}
|
||||
|
||||
inline time_t SysTimegm(struct tm* tm) {
|
||||
return timegm(tm);
|
||||
}
|
||||
|
||||
inline void SysSystemToFileTimeUtc(const SysTimeStruct& f, SysFileTimeStruct& t) {
|
||||
struct tm i {};
|
||||
|
||||
i.tm_year = f.Year - 1900;
|
||||
i.tm_mon = f.Month - 1;
|
||||
i.tm_mday = f.Day;
|
||||
i.tm_hour = f.Hour;
|
||||
i.tm_min = f.Minute;
|
||||
i.tm_sec = f.Second;
|
||||
|
||||
t.is_invalid = (f.is_invalid || (t.time = SysTimegm(&i)) == static_cast<time_t>(-1));
|
||||
}
|
||||
|
||||
// Retrieves the current local date and time.
|
||||
inline void SysGetSystemTime(SysTimeStruct& t) {
|
||||
time_t st {};
|
||||
struct tm i {};
|
||||
|
||||
if (time(&st) == static_cast<time_t>(-1) || localtime_r(&st, &i) == nullptr) {
|
||||
t.is_invalid = true;
|
||||
return;
|
||||
}
|
||||
|
||||
t.is_invalid = false;
|
||||
t.Year = i.tm_year + 1900;
|
||||
t.Month = i.tm_mon + 1;
|
||||
t.Day = i.tm_mday;
|
||||
t.Hour = i.tm_hour;
|
||||
t.Minute = i.tm_min;
|
||||
t.Second = (i.tm_sec == 60 ? 59 : i.tm_sec);
|
||||
t.Milliseconds = 0;
|
||||
}
|
||||
|
||||
// Retrieves the current system date and time in Coordinated Universal Time (UTC).
|
||||
inline void SysGetSystemTimeUtc(SysTimeStruct& t) {
|
||||
time_t st {};
|
||||
struct tm i {};
|
||||
|
||||
if (time(&st) == static_cast<time_t>(-1) || gmtime_r(&st, &i) == nullptr) {
|
||||
t.is_invalid = true;
|
||||
return;
|
||||
}
|
||||
|
||||
t.is_invalid = false;
|
||||
t.Year = i.tm_year + 1900;
|
||||
t.Month = i.tm_mon + 1;
|
||||
t.Day = i.tm_mday;
|
||||
t.Hour = i.tm_hour;
|
||||
t.Minute = i.tm_min;
|
||||
t.Second = (i.tm_sec == 60 ? 59 : i.tm_sec);
|
||||
t.Milliseconds = 0;
|
||||
}
|
||||
|
||||
inline void SysQueryPerformanceFrequency(uint64_t* freq) {
|
||||
*freq = 1000000000LL;
|
||||
}
|
||||
|
||||
inline void SysQueryPerformanceCounter(uint64_t* counter) {
|
||||
struct timespec now {};
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
*counter = now.tv_sec * 1000000000LL + now.tv_nsec;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* KYTY_COMMON_PLATFORM_SYSTIMER_H_ */
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef KYTY_COMMON_PLATFORM_SYSVIRTUAL_H_
|
||||
#define KYTY_COMMON_PLATFORM_SYSVIRTUAL_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/virtualMemory.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
void SysVirtualInit();
|
||||
|
||||
uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mode);
|
||||
uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
|
||||
uint64_t alignment);
|
||||
bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode mode);
|
||||
bool SysVirtualCommit(uint64_t address, uint64_t size, VirtualMemory::Mode mode);
|
||||
uint64_t SysVirtualReserve(uint64_t address, uint64_t size);
|
||||
uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alignment);
|
||||
bool SysVirtualReserveFixed(uint64_t address, uint64_t size);
|
||||
bool SysVirtualDecommit(uint64_t address, uint64_t size);
|
||||
bool SysVirtualFree(uint64_t address);
|
||||
bool SysVirtualFreeRange(uint64_t address, uint64_t size);
|
||||
bool SysVirtualProtect(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
|
||||
VirtualMemory::Mode* old_mode = nullptr);
|
||||
bool SysVirtualFlushInstructionCache(uint64_t address, uint64_t size);
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_PLATFORM_SYSVIRTUAL_H_ */
|
||||
@@ -0,0 +1,105 @@
|
||||
#include "common/common.h"
|
||||
|
||||
// IWYU pragma: no_include <basetsd.h>
|
||||
// IWYU pragma: no_include <memoryapi.h>
|
||||
// IWYU pragma: no_include <minwindef.h>
|
||||
// IWYU pragma: no_include <processthreadsapi.h>
|
||||
// IWYU pragma: no_include <winbase.h>
|
||||
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
// #error "KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS"
|
||||
#else
|
||||
|
||||
#include <windows.h> // IWYU pragma: keep
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/platform/sysDbg.h"
|
||||
|
||||
struct FrameS {
|
||||
struct FrameS* next;
|
||||
void* ret_addr;
|
||||
};
|
||||
|
||||
int WalkStack(int z_stack_depth, void** z_stack_trace) {
|
||||
CONTEXT context;
|
||||
// KNONVOLATILE_CONTEXT_POINTERS NvContext;
|
||||
PRUNTIME_FUNCTION runtime_function = nullptr;
|
||||
PVOID handler_data = nullptr;
|
||||
ULONG64 establisher_frame = 0;
|
||||
ULONG64 image_base = 0;
|
||||
|
||||
RtlCaptureContext(&context);
|
||||
|
||||
int frame = 0;
|
||||
|
||||
while (true) {
|
||||
if (frame >= z_stack_depth) {
|
||||
break;
|
||||
}
|
||||
|
||||
z_stack_trace[frame] = reinterpret_cast<void*>(context.Rip);
|
||||
|
||||
frame++;
|
||||
|
||||
runtime_function = RtlLookupFunctionEntry(context.Rip, &image_base, nullptr);
|
||||
|
||||
if (runtime_function == nullptr) {
|
||||
break;
|
||||
}
|
||||
|
||||
// RtlZeroMemory(&NvContext, sizeof(KNONVOLATILE_CONTEXT_POINTERS));
|
||||
RtlVirtualUnwind(0, image_base, context.Rip, runtime_function, &context, &handler_data,
|
||||
&establisher_frame, nullptr /*&NvContext*/);
|
||||
|
||||
if (context.Rip == 0u) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
void SysStackWalk(void** stack, int* depth) {
|
||||
int n = WalkStack(*depth, stack);
|
||||
*depth = n;
|
||||
// unwind_info_t info = {stack, 0, *depth};
|
||||
// _Unwind_Backtrace(&trace_fcn, &info);
|
||||
// *depth = info.depth;
|
||||
}
|
||||
|
||||
void SysStackUsagePrint(sys_dbg_stack_info_t& stack) {
|
||||
printf("stack: (0x%" PRIx64 ", %" PRIu64 ") + (0x%" PRIx64 ", %" PRIu64 ") + (0x%" PRIx64
|
||||
", %" PRIu64 ")\n",
|
||||
static_cast<uint64_t>(stack.reserved_addr), static_cast<uint64_t>(stack.reserved_size),
|
||||
static_cast<uint64_t>(stack.guard_addr), static_cast<uint64_t>(stack.guard_size),
|
||||
static_cast<uint64_t>(stack.commited_addr), static_cast<uint64_t>(stack.commited_size));
|
||||
}
|
||||
|
||||
void SysStackUsage(sys_dbg_stack_info_t& s) {
|
||||
MEMORY_BASIC_INFORMATION mbi {};
|
||||
[[maybe_unused]] size_t ss = VirtualQuery(&mbi, &mbi, sizeof(mbi));
|
||||
EXIT_IF(ss == 0);
|
||||
PVOID reserved = mbi.AllocationBase;
|
||||
ss = VirtualQuery(reserved, &mbi, sizeof(mbi));
|
||||
EXIT_IF(ss == 0);
|
||||
size_t reserved_size = mbi.RegionSize;
|
||||
ss = VirtualQuery(static_cast<char*>(reserved) + reserved_size, &mbi, sizeof(mbi));
|
||||
EXIT_IF(ss == 0);
|
||||
void* guard_page = mbi.BaseAddress;
|
||||
size_t guard_page_size = mbi.RegionSize;
|
||||
ss = VirtualQuery(static_cast<char*>(guard_page) + guard_page_size, &mbi, sizeof(mbi));
|
||||
EXIT_IF(ss == 0);
|
||||
void* commited = mbi.BaseAddress;
|
||||
size_t commited_size = mbi.RegionSize;
|
||||
s.reserved_addr = reinterpret_cast<uintptr_t>(reserved);
|
||||
s.reserved_size = reserved_size;
|
||||
s.guard_addr = reinterpret_cast<uintptr_t>(guard_page);
|
||||
s.guard_size = guard_page_size;
|
||||
s.commited_addr = reinterpret_cast<uintptr_t>(commited);
|
||||
s.commited_size = commited_size;
|
||||
|
||||
s.addr = s.reserved_addr;
|
||||
s.total_size = s.reserved_size + s.guard_size + s.commited_size;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,576 @@
|
||||
#include "common/common.h"
|
||||
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
// #error "KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS"
|
||||
#else
|
||||
|
||||
#include "common/platform/sysFileIO.h"
|
||||
#include "common/platform/sysTimer.h"
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
#include <windows.h> // IWYU pragma: keep
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
enum sys_file_type_t {
|
||||
SYS_FILE_ERROR, // NOLINT(readability-identifier-naming)
|
||||
SYS_FILE_MEMORY_STAT, // NOLINT(readability-identifier-naming)
|
||||
SYS_FILE_FILE, // NOLINT(readability-identifier-naming)
|
||||
SYS_FILE_MEMORY_DYN // NOLINT(readability-identifier-naming)
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct sys_file_mem_buf_t {
|
||||
uint8_t* base;
|
||||
uint8_t* ptr;
|
||||
uint32_t size;
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct sys_file_t {
|
||||
sys_file_type_t type;
|
||||
union {
|
||||
HANDLE handle;
|
||||
sys_file_mem_buf_t* buf;
|
||||
};
|
||||
};
|
||||
|
||||
// IWYU pragma: no_include <fileapi.h>
|
||||
// IWYU pragma: no_include <handleapi.h>
|
||||
// IWYU pragma: no_include <minwinbase.h>
|
||||
// IWYU pragma: no_include <minwindef.h>
|
||||
// IWYU pragma: no_include <winbase.h>
|
||||
|
||||
constexpr DWORD FILE_SHARE_POSIX = static_cast<DWORD>(FILE_SHARE_READ) |
|
||||
static_cast<DWORD>(FILE_SHARE_WRITE) |
|
||||
static_cast<DWORD>(FILE_SHARE_DELETE);
|
||||
|
||||
static DWORD GetCacheAccessType(sys_file_cache_type_t t) {
|
||||
if (t == SYS_FILE_CACHE_RANDOM_ACCESS) {
|
||||
return FILE_FLAG_RANDOM_ACCESS;
|
||||
}
|
||||
|
||||
if (t == SYS_FILE_CACHE_SEQUENTIAL_SCAN) {
|
||||
return SYS_FILE_CACHE_SEQUENTIAL_SCAN;
|
||||
}
|
||||
|
||||
return SYS_FILE_CACHE_AUTO;
|
||||
}
|
||||
|
||||
void SysFileRead(void* data, uint32_t size, sys_file_t& f, uint32_t* bytes_read) {
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
DWORD w = 0;
|
||||
ReadFile(f.handle, data, size, &w, nullptr);
|
||||
if (bytes_read != nullptr) {
|
||||
*bytes_read = w;
|
||||
}
|
||||
} else if (f.type == SYS_FILE_MEMORY_STAT) {
|
||||
uint32_t s = size;
|
||||
if (f.buf->size != 0u) {
|
||||
uint32_t l = f.buf->size - (f.buf->ptr - f.buf->base);
|
||||
if (s > l) {
|
||||
s = l;
|
||||
}
|
||||
}
|
||||
std::memcpy(data, f.buf->ptr, s);
|
||||
f.buf->ptr += s;
|
||||
if (bytes_read != nullptr) {
|
||||
*bytes_read = s;
|
||||
}
|
||||
} else if (f.type == SYS_FILE_MEMORY_DYN) {
|
||||
uint32_t s = size;
|
||||
if (f.buf->size != 0u) {
|
||||
uint32_t l = f.buf->size - (f.buf->ptr - f.buf->base);
|
||||
if (s > l) {
|
||||
s = l;
|
||||
}
|
||||
} else {
|
||||
s = 0;
|
||||
}
|
||||
std::memcpy(data, f.buf->ptr, s);
|
||||
f.buf->ptr += s;
|
||||
if (bytes_read != nullptr) {
|
||||
*bytes_read = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SysFileWrite(const void* data, uint32_t size, sys_file_t& f, uint32_t* bytes_written) {
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
DWORD w = 0;
|
||||
WriteFile(f.handle, data, size, &w, nullptr);
|
||||
if (bytes_written != nullptr) {
|
||||
*bytes_written = w;
|
||||
}
|
||||
} else if (f.type == SYS_FILE_MEMORY_STAT) {
|
||||
uint32_t s = size;
|
||||
if (f.buf->size != 0u) {
|
||||
uint32_t l = f.buf->size - (f.buf->ptr - f.buf->base);
|
||||
if (s > l) {
|
||||
s = l;
|
||||
}
|
||||
}
|
||||
std::memcpy(f.buf->ptr, data, s);
|
||||
f.buf->ptr += s;
|
||||
if (bytes_written != nullptr) {
|
||||
*bytes_written = s;
|
||||
}
|
||||
} else if (f.type == SYS_FILE_MEMORY_DYN) {
|
||||
uint32_t pos = f.buf->ptr - f.buf->base;
|
||||
if (f.buf->size < pos + size) {
|
||||
f.buf->base = static_cast<uint8_t*>(std::realloc(f.buf->base, pos + size));
|
||||
f.buf->ptr = f.buf->base + pos;
|
||||
f.buf->size = pos + size;
|
||||
}
|
||||
std::memcpy(f.buf->ptr, data, size);
|
||||
f.buf->ptr += size;
|
||||
if (bytes_written != nullptr) {
|
||||
*bytes_written = size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SysFileWrite(uint32_t n, sys_file_t& f) {
|
||||
SysFileWrite(&n, 4, f);
|
||||
}
|
||||
|
||||
sys_file_t* SysFileCreate(const std::filesystem::path& file_name) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
auto wide = file_name.wstring();
|
||||
HANDLE h_file = nullptr;
|
||||
h_file = CreateFileW(wide.c_str(),
|
||||
static_cast<DWORD>(GENERIC_READ) | static_cast<DWORD>(GENERIC_WRITE) |
|
||||
static_cast<DWORD>(DELETE),
|
||||
FILE_SHARE_POSIX, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
|
||||
ret->handle = h_file;
|
||||
ret->type = SYS_FILE_FILE;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpenR(const std::filesystem::path& file_name, sys_file_cache_type_t cache_type) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
auto wide = file_name.wstring();
|
||||
HANDLE h_file = nullptr;
|
||||
h_file = CreateFileW(wide.c_str(), GENERIC_READ, FILE_SHARE_POSIX, nullptr, OPEN_EXISTING,
|
||||
GetCacheAccessType(cache_type), nullptr);
|
||||
|
||||
if (h_file == INVALID_HANDLE_VALUE) {
|
||||
ret->type = SYS_FILE_ERROR;
|
||||
} else {
|
||||
ret->type = SYS_FILE_FILE;
|
||||
}
|
||||
|
||||
ret->handle = h_file;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpen(uint8_t* buf, uint32_t buf_size) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
ret->type = SYS_FILE_MEMORY_STAT;
|
||||
ret->buf = new sys_file_mem_buf_t;
|
||||
ret->buf->base = buf;
|
||||
ret->buf->ptr = buf;
|
||||
ret->buf->size = buf_size;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileCreate() {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
ret->type = SYS_FILE_MEMORY_DYN;
|
||||
ret->buf = new sys_file_mem_buf_t;
|
||||
ret->buf->base = nullptr;
|
||||
ret->buf->ptr = nullptr;
|
||||
ret->buf->size = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpenW(const std::filesystem::path& file_name, sys_file_cache_type_t cache_type) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
auto wide = file_name.wstring();
|
||||
HANDLE h_file = nullptr;
|
||||
h_file = CreateFileW(
|
||||
wide.c_str(), static_cast<DWORD>(GENERIC_WRITE) | static_cast<DWORD>(DELETE),
|
||||
FILE_SHARE_POSIX, nullptr, OPEN_EXISTING, GetCacheAccessType(cache_type), nullptr);
|
||||
|
||||
if (h_file == INVALID_HANDLE_VALUE) {
|
||||
ret->type = SYS_FILE_ERROR;
|
||||
} else {
|
||||
ret->type = SYS_FILE_FILE;
|
||||
}
|
||||
|
||||
ret->handle = h_file;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpenRw(const std::filesystem::path& file_name,
|
||||
sys_file_cache_type_t cache_type) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
auto wide = file_name.wstring();
|
||||
HANDLE h_file = nullptr;
|
||||
h_file = CreateFileW(wide.c_str(),
|
||||
static_cast<DWORD>(GENERIC_READ) | static_cast<DWORD>(GENERIC_WRITE) |
|
||||
static_cast<DWORD>(DELETE),
|
||||
FILE_SHARE_POSIX, nullptr, OPEN_EXISTING, GetCacheAccessType(cache_type),
|
||||
nullptr);
|
||||
|
||||
if (h_file == INVALID_HANDLE_VALUE) {
|
||||
ret->type = SYS_FILE_ERROR;
|
||||
} else {
|
||||
ret->type = SYS_FILE_FILE;
|
||||
}
|
||||
|
||||
ret->handle = h_file;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SysFileClose(sys_file_t* f) {
|
||||
if (f->type == SYS_FILE_FILE) {
|
||||
CloseHandle(f->handle);
|
||||
} else if (f->type == SYS_FILE_MEMORY_STAT) {
|
||||
delete f->buf;
|
||||
} else if (f->type == SYS_FILE_MEMORY_DYN) {
|
||||
std::free(f->buf->base);
|
||||
delete f->buf;
|
||||
}
|
||||
|
||||
// f.type = SYS_FILE_ERROR;
|
||||
delete f;
|
||||
}
|
||||
|
||||
uint64_t SysFileSize(sys_file_t& f) {
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
LARGE_INTEGER s;
|
||||
GetFileSizeEx(f.handle, &s);
|
||||
return s.QuadPart;
|
||||
}
|
||||
|
||||
if (f.type == SYS_FILE_MEMORY_STAT || f.type == SYS_FILE_MEMORY_DYN) {
|
||||
return f.buf->size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t SysFileSize(const std::filesystem::path& file_name) {
|
||||
LARGE_INTEGER s;
|
||||
WIN32_FILE_ATTRIBUTE_DATA a;
|
||||
|
||||
auto wide = file_name.wstring();
|
||||
if (GetFileAttributesExW(wide.c_str(), GetFileExInfoStandard, &a) == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
s.HighPart = static_cast<LONG>(a.nFileSizeHigh);
|
||||
s.LowPart = a.nFileSizeLow;
|
||||
|
||||
return s.QuadPart;
|
||||
}
|
||||
|
||||
bool SysFileTruncate(sys_file_t& f, uint64_t size) {
|
||||
bool ok = false;
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
LARGE_INTEGER s {};
|
||||
LARGE_INTEGER r {};
|
||||
s.QuadPart = 0;
|
||||
SetFilePointerEx(f.handle, s, &r, FILE_CURRENT);
|
||||
s.QuadPart = static_cast<LONGLONG>(size);
|
||||
ok = (SetFilePointerEx(f.handle, s, nullptr, FILE_BEGIN) != 0 &&
|
||||
SetEndOfFile(f.handle) != 0);
|
||||
SetFilePointerEx(f.handle, r, nullptr, FILE_BEGIN);
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool SysFileUnlink(sys_file_t& f, const std::filesystem::path& name) {
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
FILE_DISPOSITION_INFO info {};
|
||||
info.DeleteFile = TRUE;
|
||||
if (SetFileInformationByHandle(f.handle, FileDispositionInfo, &info, sizeof(info)) != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return SysFileDeleteFile(name);
|
||||
}
|
||||
|
||||
bool SysFileSeek(sys_file_t& f, uint64_t offset) {
|
||||
bool ok = true;
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
LARGE_INTEGER s;
|
||||
s.QuadPart = static_cast<LONGLONG>(offset);
|
||||
ok = (SetFilePointerEx(f.handle, s, nullptr, FILE_BEGIN) != 0);
|
||||
// printf("seek: %u\n", offset);
|
||||
} else if (f.type == SYS_FILE_MEMORY_STAT || f.type == SYS_FILE_MEMORY_DYN) {
|
||||
f.buf->ptr = f.buf->base + offset;
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
uint64_t SysFileTell(sys_file_t& f) {
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
LARGE_INTEGER s {};
|
||||
LARGE_INTEGER r {};
|
||||
s.QuadPart = 0;
|
||||
SetFilePointerEx(f.handle, s, &r, FILE_CURRENT);
|
||||
// printf("tell: %u\n", r.QuadPart);
|
||||
return r.QuadPart;
|
||||
}
|
||||
|
||||
if (f.type == SYS_FILE_MEMORY_STAT || f.type == SYS_FILE_MEMORY_DYN) {
|
||||
return f.buf->ptr - f.buf->base;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool SysFileIsError(sys_file_t& f) {
|
||||
return f.type == SYS_FILE_ERROR ||
|
||||
(f.type == SYS_FILE_FILE && f.handle == INVALID_HANDLE_VALUE);
|
||||
}
|
||||
|
||||
bool SysFileIsDirectoryExisting(const std::filesystem::path& path) {
|
||||
auto wide = path.wstring();
|
||||
DWORD a = GetFileAttributesW(wide.c_str());
|
||||
return a != INVALID_FILE_ATTRIBUTES &&
|
||||
((a & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) != 0u);
|
||||
}
|
||||
|
||||
bool SysFileIsFileExisting(const std::filesystem::path& name) {
|
||||
auto wide = name.wstring();
|
||||
DWORD a = GetFileAttributesW(wide.c_str());
|
||||
return a != INVALID_FILE_ATTRIBUTES &&
|
||||
((a & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) == 0u);
|
||||
}
|
||||
|
||||
bool SysFileCreateDirectory(const std::filesystem::path& path) {
|
||||
auto wide = path.wstring();
|
||||
return CreateDirectoryW(wide.c_str(), nullptr) != 0;
|
||||
}
|
||||
|
||||
bool SysFileDeleteDirectory(const std::filesystem::path& path) {
|
||||
auto wide = path.wstring();
|
||||
return RemoveDirectoryW(wide.c_str()) != 0;
|
||||
}
|
||||
|
||||
bool SysFileDeleteFile(const std::filesystem::path& name) {
|
||||
auto wide = name.wstring();
|
||||
return DeleteFileW(wide.c_str()) != 0;
|
||||
}
|
||||
|
||||
bool SysFileFlush(sys_file_t& f) {
|
||||
if (f.type == SYS_FILE_FILE && f.handle != INVALID_HANDLE_VALUE) {
|
||||
return (FlushFileBuffers(f.handle) != 0);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
SysFileTimeStruct SysFileGetLastAccessTimeUtc(const std::filesystem::path& name) {
|
||||
SysFileTimeStruct r {};
|
||||
sys_file_t* f = SysFileOpenR(name);
|
||||
r.is_invalid =
|
||||
(f->type == SYS_FILE_ERROR || (GetFileTime(f->handle, nullptr, &r.time, nullptr) == 0));
|
||||
SysFileClose(f);
|
||||
return r;
|
||||
}
|
||||
|
||||
SysFileTimeStruct SysFileGetLastWriteTimeUtc(const std::filesystem::path& name) {
|
||||
SysFileTimeStruct r {};
|
||||
sys_file_t* f = SysFileOpenR(name);
|
||||
r.is_invalid =
|
||||
(f->type == SYS_FILE_ERROR || (GetFileTime(f->handle, nullptr, nullptr, &r.time) == 0));
|
||||
SysFileClose(f);
|
||||
return r;
|
||||
}
|
||||
|
||||
void SysFileGetLastAccessAndWriteTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& a,
|
||||
SysFileTimeStruct& w) {
|
||||
// TODO() open file with dwDesiredAccess = 0
|
||||
// TODO() open directory
|
||||
sys_file_t* f = SysFileOpenR(name);
|
||||
a.is_invalid = w.is_invalid =
|
||||
(f->type == SYS_FILE_ERROR || (GetFileTime(f->handle, nullptr, &a.time, &w.time) == 0));
|
||||
SysFileClose(f);
|
||||
}
|
||||
|
||||
void SysFileGetLastAccessAndWriteTimeUtc(sys_file_t& f, SysFileTimeStruct& a,
|
||||
SysFileTimeStruct& w) {
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
a.is_invalid = w.is_invalid = (GetFileTime(f.handle, nullptr, &a.time, &w.time) == 0);
|
||||
} else if (f.type == SYS_FILE_MEMORY_STAT || f.type == SYS_FILE_MEMORY_DYN) {
|
||||
SysTimeStruct t {};
|
||||
SysGetSystemTimeUtc(t);
|
||||
SysSystemToFileTimeUtc(t, a);
|
||||
SysSystemToFileTimeUtc(t, w);
|
||||
} else {
|
||||
a.is_invalid = w.is_invalid = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool SysFileSetLastAccessTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& access) {
|
||||
if (access.is_invalid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
|
||||
sys_file_t* f = SysFileOpenW(name);
|
||||
|
||||
ok = !(f->type == SYS_FILE_ERROR ||
|
||||
(SetFileTime(f->handle, nullptr, &access.time, nullptr) == 0));
|
||||
|
||||
SysFileClose(f);
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool SysFileSetLastWriteTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& write) {
|
||||
if (write.is_invalid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
|
||||
sys_file_t* f = SysFileOpenW(name);
|
||||
|
||||
ok = !(f->type == SYS_FILE_ERROR ||
|
||||
(SetFileTime(f->handle, nullptr, nullptr, &write.time) == 0));
|
||||
|
||||
SysFileClose(f);
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
|
||||
SysFileTimeStruct& access, SysFileTimeStruct& write) {
|
||||
if (access.is_invalid || write.is_invalid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
|
||||
sys_file_t* f = SysFileOpenW(name);
|
||||
|
||||
ok = !(f->type == SYS_FILE_ERROR ||
|
||||
(SetFileTime(f->handle, nullptr, &access.time, &write.time) == 0));
|
||||
|
||||
SysFileClose(f);
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_find_t>& out) {
|
||||
std::string real_path = Common::ReplaceChar(Common::PathToGenericString(path), '\\', '/');
|
||||
if (!Common::EndsWith(real_path, "/")) {
|
||||
real_path += "/";
|
||||
}
|
||||
|
||||
std::string pattern = real_path + "*";
|
||||
|
||||
HANDLE h = nullptr;
|
||||
WIN32_FIND_DATAW data;
|
||||
|
||||
auto wide_pattern = std::filesystem::path(pattern).wstring();
|
||||
h = FindFirstFileW(wide_pattern.c_str(), &data);
|
||||
|
||||
if (h == INVALID_HANDLE_VALUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
std::filesystem::path file_name(data.cFileName);
|
||||
auto file_name_str = Common::PathToString(file_name);
|
||||
|
||||
if (file_name_str == "." || file_name_str == "..") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((data.dwFileAttributes & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) != 0u) {
|
||||
SysFileFindFiles(std::filesystem::path(real_path) / file_name, out);
|
||||
} else {
|
||||
sys_file_find_t r {};
|
||||
|
||||
r.path_with_name = std::filesystem::path(real_path) / file_name;
|
||||
r.size = (static_cast<uint64_t>(data.nFileSizeHigh) << 32u) +
|
||||
static_cast<uint64_t>(data.nFileSizeLow);
|
||||
r.last_access_time.is_invalid = false;
|
||||
r.last_access_time.time = data.ftLastAccessTime;
|
||||
r.last_write_time.is_invalid = false;
|
||||
r.last_write_time.time = data.ftLastWriteTime;
|
||||
|
||||
out.push_back(r);
|
||||
}
|
||||
|
||||
} while (FindNextFileW(h, &data) != 0);
|
||||
|
||||
FindClose(h);
|
||||
}
|
||||
|
||||
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out) {
|
||||
std::string real_path = Common::ReplaceChar(Common::PathToGenericString(path), '\\', '/');
|
||||
if (!Common::EndsWith(real_path, "/")) {
|
||||
real_path += "/";
|
||||
}
|
||||
|
||||
std::string pattern = real_path + "*";
|
||||
|
||||
HANDLE h = nullptr;
|
||||
WIN32_FIND_DATAW data;
|
||||
|
||||
auto wide_pattern = std::filesystem::path(pattern).wstring();
|
||||
h = FindFirstFileW(wide_pattern.c_str(), &data);
|
||||
|
||||
if (h == INVALID_HANDLE_VALUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
std::filesystem::path file_name(data.cFileName);
|
||||
|
||||
sys_dir_entry_t r {};
|
||||
|
||||
r.is_file = ((data.dwFileAttributes & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) == 0u);
|
||||
r.name = Common::PathToString(file_name);
|
||||
|
||||
out.push_back(r);
|
||||
|
||||
} while (FindNextFileW(h, &data) != 0);
|
||||
|
||||
FindClose(h);
|
||||
}
|
||||
|
||||
bool SysFileCopyFile(const std::filesystem::path& src, const std::filesystem::path& dst) {
|
||||
auto src_wide = src.wstring();
|
||||
auto dst_wide = dst.wstring();
|
||||
return CopyFileW(src_wide.c_str(), dst_wide.c_str(), FALSE) != 0;
|
||||
}
|
||||
|
||||
bool SysFileMoveFile(const std::filesystem::path& src, const std::filesystem::path& dst) {
|
||||
auto src_wide = src.wstring();
|
||||
auto dst_wide = dst.wstring();
|
||||
return MoveFileW(src_wide.c_str(), dst_wide.c_str()) != 0;
|
||||
}
|
||||
|
||||
void SysFileRemoveReadonly(const std::filesystem::path& name) {
|
||||
auto wide = name.wstring();
|
||||
SetFileAttributesW(wide.c_str(), GetFileAttributesW(wide.c_str()) &
|
||||
(~static_cast<DWORD>(FILE_ATTRIBUTE_READONLY)));
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,355 @@
|
||||
#include "common/common.h"
|
||||
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
// #error "KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS"
|
||||
#else
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/platform/sysVirtual.h"
|
||||
#include "common/virtualMemory.h"
|
||||
|
||||
#include <windows.h> // IWYU pragma: keep
|
||||
|
||||
// IWYU pragma: no_include <basetsd.h>
|
||||
// IWYU pragma: no_include <errhandlingapi.h>
|
||||
// IWYU pragma: no_include <memoryapi.h>
|
||||
// IWYU pragma: no_include <minwindef.h>
|
||||
// IWYU pragma: no_include <processthreadsapi.h>
|
||||
// IWYU pragma: no_include <winbase.h>
|
||||
// IWYU pragma: no_include <winerror.h>
|
||||
// IWYU pragma: no_include <wtypes.h>
|
||||
|
||||
namespace Common {
|
||||
|
||||
static DWORD GetProtectionFlag(VirtualMemory::Mode mode) {
|
||||
DWORD protect = PAGE_NOACCESS;
|
||||
switch (mode) {
|
||||
case VirtualMemory::Mode::Read: protect = PAGE_READONLY; break;
|
||||
|
||||
case VirtualMemory::Mode::Write:
|
||||
case VirtualMemory::Mode::ReadWrite: protect = PAGE_READWRITE; break;
|
||||
|
||||
case VirtualMemory::Mode::Execute: protect = PAGE_EXECUTE; break;
|
||||
|
||||
case VirtualMemory::Mode::ExecuteRead: protect = PAGE_EXECUTE_READ; break;
|
||||
|
||||
case VirtualMemory::Mode::ExecuteWrite:
|
||||
case VirtualMemory::Mode::ExecuteReadWrite: protect = PAGE_EXECUTE_READWRITE; break;
|
||||
|
||||
case VirtualMemory::Mode::NoAccess:
|
||||
default: protect = PAGE_NOACCESS; break;
|
||||
}
|
||||
return protect;
|
||||
}
|
||||
|
||||
static VirtualMemory::Mode GetProtectionFlag(DWORD mode) {
|
||||
switch (mode) {
|
||||
case PAGE_NOACCESS: return VirtualMemory::Mode::NoAccess;
|
||||
case PAGE_READONLY: return VirtualMemory::Mode::Read;
|
||||
case PAGE_READWRITE: return VirtualMemory::Mode::ReadWrite;
|
||||
case PAGE_EXECUTE: return VirtualMemory::Mode::Execute;
|
||||
case PAGE_EXECUTE_READ: return VirtualMemory::Mode::ExecuteRead;
|
||||
case PAGE_EXECUTE_READWRITE: return VirtualMemory::Mode::ExecuteReadWrite;
|
||||
default: return VirtualMemory::Mode::NoAccess;
|
||||
}
|
||||
}
|
||||
|
||||
void SysVirtualInit() {}
|
||||
|
||||
uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
auto ptr = (address == 0 ? SysVirtualAllocAligned(address, size, mode, 1)
|
||||
: reinterpret_cast<uintptr_t>(VirtualAlloc(
|
||||
reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||
static_cast<DWORD>(MEM_COMMIT) | static_cast<DWORD>(MEM_RESERVE),
|
||||
GetProtectionFlag(mode))));
|
||||
if (ptr == 0) {
|
||||
auto err = static_cast<uint32_t>(GetLastError());
|
||||
|
||||
if (err != ERROR_INVALID_ADDRESS) {
|
||||
printf("VirtualAlloc() failed: 0x%08" PRIx32 "\n", err);
|
||||
} else {
|
||||
return SysVirtualAllocAligned(address, size, mode, 1);
|
||||
}
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
using VirtualAlloc2_func_t = /*WINBASEAPI*/ PVOID WINAPI (*)(HANDLE, PVOID, SIZE_T, ULONG, ULONG,
|
||||
MEM_EXTENDED_PARAMETER*, ULONG);
|
||||
|
||||
static VirtualAlloc2_func_t ResolveVirtualAlloc2() {
|
||||
HMODULE h = GetModuleHandle("KernelBase"); // @suppress("Invalid arguments")
|
||||
if (h != nullptr) {
|
||||
return reinterpret_cast<VirtualAlloc2_func_t>(GetProcAddress(h, "VirtualAlloc2"));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static uint64_t AlignUp(uint64_t addr, uint64_t alignment) {
|
||||
return (addr + alignment - 1) & ~(alignment - 1);
|
||||
}
|
||||
|
||||
uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
|
||||
uint64_t alignment) {
|
||||
if (alignment == 0) {
|
||||
printf("VirtualAlloc2 failed: 0x%08" PRIx32 "\n", static_cast<uint32_t>(GetLastError()));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static constexpr uint64_t SYSTEM_MANAGED_MIN = 0x0000040000u;
|
||||
static constexpr uint64_t SYSTEM_MANAGED_MAX = 0x07FFFFBFFFu;
|
||||
static constexpr uint64_t USER_MIN = 0x1000000000u;
|
||||
static constexpr uint64_t USER_MAX = 0xFBFFFFFFFFu;
|
||||
|
||||
MEM_ADDRESS_REQUIREMENTS req {};
|
||||
MEM_EXTENDED_PARAMETER param {};
|
||||
req.LowestStartingAddress =
|
||||
(address == 0 ? reinterpret_cast<PVOID>(SYSTEM_MANAGED_MIN)
|
||||
: reinterpret_cast<PVOID>(AlignUp(address, alignment)));
|
||||
req.HighestEndingAddress = (address == 0 ? reinterpret_cast<PVOID>(SYSTEM_MANAGED_MAX)
|
||||
: reinterpret_cast<PVOID>(USER_MAX));
|
||||
req.Alignment = alignment;
|
||||
param.Type = MemExtendedParameterAddressRequirements;
|
||||
param.Pointer = &req;
|
||||
|
||||
MEM_ADDRESS_REQUIREMENTS req2 {};
|
||||
MEM_EXTENDED_PARAMETER param2 {};
|
||||
req2.LowestStartingAddress =
|
||||
(address == 0 ? reinterpret_cast<PVOID>(USER_MIN)
|
||||
: reinterpret_cast<PVOID>(AlignUp(address, alignment)));
|
||||
req2.HighestEndingAddress = reinterpret_cast<PVOID>(USER_MAX);
|
||||
req2.Alignment = alignment;
|
||||
param2.Type = MemExtendedParameterAddressRequirements;
|
||||
param2.Pointer = &req2;
|
||||
|
||||
static auto virtual_alloc2 = ResolveVirtualAlloc2();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(virtual_alloc2 == nullptr);
|
||||
|
||||
auto ptr = reinterpret_cast<uintptr_t>(
|
||||
virtual_alloc2(GetCurrentProcess(), nullptr, size,
|
||||
static_cast<DWORD>(MEM_COMMIT) | static_cast<DWORD>(MEM_RESERVE),
|
||||
GetProtectionFlag(mode), ¶m, 1));
|
||||
|
||||
if (ptr == 0) {
|
||||
ptr = reinterpret_cast<uintptr_t>(
|
||||
virtual_alloc2(GetCurrentProcess(), nullptr, size,
|
||||
static_cast<DWORD>(MEM_COMMIT) | static_cast<DWORD>(MEM_RESERVE),
|
||||
GetProtectionFlag(mode), ¶m2, 1));
|
||||
}
|
||||
|
||||
if (ptr == 0) {
|
||||
auto err = static_cast<uint32_t>(GetLastError());
|
||||
if (err != ERROR_INVALID_PARAMETER) {
|
||||
printf("VirtualAlloc2(alignment = 0x%016" PRIx64 ") failed: 0x%08" PRIx32 "\n",
|
||||
alignment, err);
|
||||
} else {
|
||||
return SysVirtualAllocAligned(address, size, mode, alignment << 1u);
|
||||
}
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
auto ptr = reinterpret_cast<uintptr_t>(VirtualAlloc(
|
||||
reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||
static_cast<DWORD>(MEM_COMMIT) | static_cast<DWORD>(MEM_RESERVE), GetProtectionFlag(mode)));
|
||||
if (ptr == 0) {
|
||||
auto err = static_cast<uint32_t>(GetLastError());
|
||||
|
||||
printf("VirtualAlloc() failed: 0x%08" PRIx32 "\n", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ptr != address) {
|
||||
printf("VirtualAlloc() failed: wrong address\n");
|
||||
VirtualFree(reinterpret_cast<LPVOID>(ptr), 0, MEM_RELEASE);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SysVirtualCommit(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
auto ptr = reinterpret_cast<uintptr_t>(
|
||||
VirtualAlloc(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||
static_cast<DWORD>(MEM_COMMIT), GetProtectionFlag(mode)));
|
||||
if (ptr == 0) {
|
||||
printf("VirtualAlloc(MEM_COMMIT) failed: 0x%08" PRIx32 "\n",
|
||||
static_cast<uint32_t>(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ptr != address) {
|
||||
printf("VirtualAlloc(MEM_COMMIT) failed: wrong address\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t SysVirtualReserve(uint64_t address, uint64_t size) {
|
||||
auto ptr = (address == 0 ? SysVirtualReserveAligned(address, size, 1)
|
||||
: reinterpret_cast<uintptr_t>(VirtualAlloc(
|
||||
reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||
static_cast<DWORD>(MEM_RESERVE), PAGE_NOACCESS)));
|
||||
if (ptr == 0) {
|
||||
auto err = static_cast<uint32_t>(GetLastError());
|
||||
|
||||
if (err != ERROR_INVALID_ADDRESS) {
|
||||
printf("VirtualAlloc(MEM_RESERVE) failed: 0x%08" PRIx32 "\n", err);
|
||||
} else {
|
||||
return SysVirtualReserveAligned(address, size, 1);
|
||||
}
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alignment) {
|
||||
if (alignment == 0) {
|
||||
printf("VirtualAlloc2(MEM_RESERVE) failed: 0x%08" PRIx32 "\n",
|
||||
static_cast<uint32_t>(GetLastError()));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static constexpr uint64_t SYSTEM_MANAGED_MIN = 0x0000040000u;
|
||||
static constexpr uint64_t SYSTEM_MANAGED_MAX = 0x07FFFFBFFFu;
|
||||
static constexpr uint64_t USER_MIN = 0x1000000000u;
|
||||
static constexpr uint64_t USER_MAX = 0xFBFFFFFFFFu;
|
||||
|
||||
MEM_ADDRESS_REQUIREMENTS req {};
|
||||
MEM_EXTENDED_PARAMETER param {};
|
||||
req.LowestStartingAddress =
|
||||
(address == 0 ? reinterpret_cast<PVOID>(SYSTEM_MANAGED_MIN)
|
||||
: reinterpret_cast<PVOID>(AlignUp(address, alignment)));
|
||||
req.HighestEndingAddress = (address == 0 ? reinterpret_cast<PVOID>(SYSTEM_MANAGED_MAX)
|
||||
: reinterpret_cast<PVOID>(USER_MAX));
|
||||
req.Alignment = alignment;
|
||||
param.Type = MemExtendedParameterAddressRequirements;
|
||||
param.Pointer = &req;
|
||||
|
||||
MEM_ADDRESS_REQUIREMENTS req2 {};
|
||||
MEM_EXTENDED_PARAMETER param2 {};
|
||||
req2.LowestStartingAddress =
|
||||
(address == 0 ? reinterpret_cast<PVOID>(USER_MIN)
|
||||
: reinterpret_cast<PVOID>(AlignUp(address, alignment)));
|
||||
req2.HighestEndingAddress = reinterpret_cast<PVOID>(USER_MAX);
|
||||
req2.Alignment = alignment;
|
||||
param2.Type = MemExtendedParameterAddressRequirements;
|
||||
param2.Pointer = &req2;
|
||||
|
||||
static auto virtual_alloc2 = ResolveVirtualAlloc2();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(virtual_alloc2 == nullptr);
|
||||
|
||||
auto ptr = reinterpret_cast<uintptr_t>(virtual_alloc2(GetCurrentProcess(), nullptr, size,
|
||||
static_cast<DWORD>(MEM_RESERVE),
|
||||
PAGE_NOACCESS, ¶m, 1));
|
||||
|
||||
if (ptr == 0) {
|
||||
ptr = reinterpret_cast<uintptr_t>(virtual_alloc2(GetCurrentProcess(), nullptr, size,
|
||||
static_cast<DWORD>(MEM_RESERVE),
|
||||
PAGE_NOACCESS, ¶m2, 1));
|
||||
}
|
||||
|
||||
if (ptr == 0) {
|
||||
auto err = static_cast<uint32_t>(GetLastError());
|
||||
if (err != ERROR_INVALID_PARAMETER) {
|
||||
printf("VirtualAlloc2(MEM_RESERVE, alignment = 0x%016" PRIx64 ") failed: 0x%08" PRIx32
|
||||
"\n",
|
||||
alignment, err);
|
||||
} else {
|
||||
return SysVirtualReserveAligned(address, size, alignment << 1u);
|
||||
}
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
bool SysVirtualReserveFixed(uint64_t address, uint64_t size) {
|
||||
auto ptr = reinterpret_cast<uintptr_t>(
|
||||
VirtualAlloc(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||
static_cast<DWORD>(MEM_RESERVE), PAGE_NOACCESS));
|
||||
if (ptr == 0) {
|
||||
printf("VirtualAlloc(MEM_RESERVE) failed: address=0x%016" PRIx64 ", size=0x%016" PRIx64
|
||||
", err=0x%08" PRIx32 "\n",
|
||||
address, size, static_cast<uint32_t>(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ptr != address) {
|
||||
printf("VirtualAlloc(MEM_RESERVE) failed: wrong address\n");
|
||||
VirtualFree(reinterpret_cast<LPVOID>(ptr), 0, MEM_RELEASE);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SysVirtualDecommit(uint64_t address, uint64_t size) {
|
||||
if (VirtualFree(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||
MEM_DECOMMIT) == 0) {
|
||||
printf("VirtualFree(MEM_DECOMMIT) failed: 0x%08" PRIx32 "\n",
|
||||
static_cast<uint32_t>(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SysVirtualFree(uint64_t address) {
|
||||
if (VirtualFree(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), 0, MEM_RELEASE) ==
|
||||
0) {
|
||||
printf("VirtualFree() failed: 0x%08" PRIx32 "\n", static_cast<uint32_t>(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SysVirtualFreeRange(uint64_t address, uint64_t size) {
|
||||
if (address == 0 || size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t current = address;
|
||||
while (current - address < size) {
|
||||
MEMORY_BASIC_INFORMATION info {};
|
||||
if (VirtualQuery(reinterpret_cast<const void*>(current), &info, sizeof(info)) == 0 ||
|
||||
reinterpret_cast<uint64_t>(info.AllocationBase) != address || info.State == MEM_FREE) {
|
||||
return false;
|
||||
}
|
||||
const auto next = reinterpret_cast<uint64_t>(info.BaseAddress) + info.RegionSize;
|
||||
if (next <= current || next - address > size) {
|
||||
return false;
|
||||
}
|
||||
current = next;
|
||||
}
|
||||
return current - address == size && SysVirtualFree(address);
|
||||
}
|
||||
|
||||
bool SysVirtualProtect(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
|
||||
VirtualMemory::Mode* old_mode) {
|
||||
DWORD old_protect = 0;
|
||||
if (VirtualProtect(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||
GetProtectionFlag(mode), &old_protect) == 0) {
|
||||
printf("VirtualProtect() failed: 0x%08" PRIx32 "\n", static_cast<uint32_t>(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
if (old_mode != nullptr) {
|
||||
*old_mode = GetProtectionFlag(old_protect);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SysVirtualFlushInstructionCache(uint64_t address, uint64_t size) {
|
||||
if (::FlushInstructionCache(GetCurrentProcess(),
|
||||
reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)),
|
||||
size) == 0) {
|
||||
printf("FlushInstructionCache() failed: 0x%08" PRIx32 "\n",
|
||||
static_cast<uint32_t>(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,89 @@
|
||||
#include "common/profiler.h"
|
||||
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/subsystems.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <common/TracyProtocol.hpp>
|
||||
#include <common/TracyVersion.hpp>
|
||||
#include <cstdio>
|
||||
#include <tracy/Tracy.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
thread_local std::vector<Profiler::ScopedBlock*> g_block_stack;
|
||||
|
||||
void RemoveBlock(Profiler::ScopedBlock* block) {
|
||||
auto block_it = std::find(g_block_stack.rbegin(), g_block_stack.rend(), block);
|
||||
if (block_it != g_block_stack.rend()) {
|
||||
g_block_stack.erase(std::next(block_it).base());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Profiler {
|
||||
|
||||
ScopedBlock::ScopedBlock(const tracy::SourceLocationData* source_location) {
|
||||
if (tracy::ProfilerAvailable()) {
|
||||
m_zone.emplace(source_location, TRACY_CALLSTACK, true);
|
||||
g_block_stack.push_back(this);
|
||||
}
|
||||
}
|
||||
|
||||
ScopedBlock::~ScopedBlock() {
|
||||
End();
|
||||
}
|
||||
|
||||
void ScopedBlock::End() {
|
||||
if (m_zone.has_value()) {
|
||||
m_zone.reset();
|
||||
RemoveBlock(this);
|
||||
}
|
||||
}
|
||||
|
||||
void EndBlock() {
|
||||
if (!g_block_stack.empty()) {
|
||||
g_block_stack.back()->End();
|
||||
}
|
||||
}
|
||||
|
||||
void SetThreadName(const char* name) {
|
||||
if (tracy::ProfilerAvailable() && name != nullptr) {
|
||||
tracy::SetThreadName(name);
|
||||
}
|
||||
}
|
||||
|
||||
void Close() {
|
||||
if (tracy::ProfilerAvailable()) {
|
||||
tracy::ShutdownProfiler();
|
||||
}
|
||||
}
|
||||
|
||||
KYTY_SUBSYSTEM_INIT(Profiler) {
|
||||
switch (Config::GetProfilerDirection()) {
|
||||
case Config::ProfilerDirection::Network:
|
||||
if (!tracy::ProfilerAvailable()) {
|
||||
tracy::StartupProfiler();
|
||||
TracySetProgramName("KytyPS5");
|
||||
::printf("Tracy profiler enabled: client %d.%d.%d, protocol %u, "
|
||||
"broadcast %u, connect to 127.0.0.1:8086\n",
|
||||
tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch,
|
||||
tracy::ProtocolVersion, tracy::BroadcastVersion);
|
||||
}
|
||||
break;
|
||||
case Config::ProfilerDirection::None:
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Profiler) {
|
||||
Close();
|
||||
}
|
||||
|
||||
KYTY_SUBSYSTEM_DESTROY(Profiler) {
|
||||
Close();
|
||||
}
|
||||
|
||||
} // namespace Profiler
|
||||
@@ -0,0 +1,78 @@
|
||||
#ifndef KYTY_COMMON_PROFILER_H_
|
||||
#define KYTY_COMMON_PROFILER_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/subsystems.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <tracy/Tracy.hpp> // IWYU pragma: export
|
||||
|
||||
namespace profiler::colors {
|
||||
|
||||
inline constexpr uint32_t RedA100 = 0xff8a80;
|
||||
inline constexpr uint32_t Blue300 = 0x64b5f6;
|
||||
inline constexpr uint32_t CyanA700 = 0x00b8d4;
|
||||
inline constexpr uint32_t Green100 = 0xc8e6c9;
|
||||
inline constexpr uint32_t Green200 = 0xa5d6a7;
|
||||
inline constexpr uint32_t Green300 = 0x81c784;
|
||||
inline constexpr uint32_t Green400 = 0x66bb6a;
|
||||
inline constexpr uint32_t Amber300 = 0xffd54f;
|
||||
inline constexpr uint32_t DeepOrangeA200 = 0xff6e40;
|
||||
|
||||
} // namespace profiler::colors
|
||||
|
||||
namespace Profiler {
|
||||
|
||||
class ScopedBlock {
|
||||
public:
|
||||
explicit ScopedBlock(const tracy::SourceLocationData* source_location);
|
||||
ScopedBlock(const ScopedBlock&) = delete;
|
||||
ScopedBlock& operator=(const ScopedBlock&) = delete;
|
||||
ScopedBlock(ScopedBlock&&) = delete;
|
||||
ScopedBlock& operator=(ScopedBlock&&) = delete;
|
||||
~ScopedBlock();
|
||||
|
||||
void End();
|
||||
|
||||
private:
|
||||
std::optional<tracy::ScopedZone> m_zone;
|
||||
};
|
||||
|
||||
void EndBlock();
|
||||
void SetThreadName(const char* name);
|
||||
|
||||
KYTY_SUBSYSTEM_DEFINE(Profiler);
|
||||
|
||||
} // namespace Profiler
|
||||
|
||||
#define KYTY_PROFILER_CONCAT_IMPL(a, b) a##b
|
||||
#define KYTY_PROFILER_CONCAT(a, b) KYTY_PROFILER_CONCAT_IMPL(a, b)
|
||||
#define KYTY_PROFILER_COLOR_OR_DEFAULT(default_color, ...) \
|
||||
KYTY_PROFILER_COLOR_OR_DEFAULT_IMPL(default_color __VA_OPT__(, ) __VA_ARGS__, default_color)
|
||||
#define KYTY_PROFILER_COLOR_OR_DEFAULT_IMPL(default_color, color, ...) color
|
||||
|
||||
#define KYTY_PROFILER_BLOCK(name, ...) \
|
||||
KYTY_PROFILER_BLOCK_IMPL(__LINE__, name __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define KYTY_PROFILER_BLOCK_IMPL(line, name, ...) \
|
||||
static constexpr tracy::SourceLocationData KYTY_PROFILER_CONCAT( \
|
||||
kyty_profiler_source_location_, line) {name, TracyFunction, TracyFile, \
|
||||
static_cast<uint32_t>(line), \
|
||||
KYTY_PROFILER_COLOR_OR_DEFAULT(0, __VA_ARGS__)}; \
|
||||
Profiler::ScopedBlock KYTY_PROFILER_CONCAT(kyty_profiler_block_, line)( \
|
||||
&KYTY_PROFILER_CONCAT(kyty_profiler_source_location_, line))
|
||||
|
||||
#define KYTY_PROFILER_FUNCTION(...) KYTY_PROFILER_FUNCTION_IMPL(__LINE__ __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define KYTY_PROFILER_FUNCTION_IMPL(line, ...) \
|
||||
static constexpr tracy::SourceLocationData KYTY_PROFILER_CONCAT( \
|
||||
kyty_profiler_source_location_, line) {nullptr, TracyFunction, TracyFile, \
|
||||
static_cast<uint32_t>(line), \
|
||||
KYTY_PROFILER_COLOR_OR_DEFAULT(0, __VA_ARGS__)}; \
|
||||
Profiler::ScopedBlock KYTY_PROFILER_CONCAT(kyty_profiler_block_, line)( \
|
||||
&KYTY_PROFILER_CONCAT(kyty_profiler_source_location_, line))
|
||||
|
||||
#define KYTY_PROFILER_END_BLOCK Profiler::EndBlock()
|
||||
|
||||
#define KYTY_PROFILER_THREAD(name) Profiler::SetThreadName(name)
|
||||
|
||||
#endif /* KYTY_COMMON_PROFILER_H_ */
|
||||
@@ -0,0 +1,36 @@
|
||||
#ifndef KYTY_COMMON_SINGLETON_H_
|
||||
#define KYTY_COMMON_SINGLETON_H_
|
||||
|
||||
#include <cstdlib>
|
||||
#include <new>
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <class T>
|
||||
class Singleton {
|
||||
public:
|
||||
static T* Instance() {
|
||||
if (!g_m_instance) {
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc,hicpp-no-malloc)
|
||||
g_m_instance = static_cast<T*>(std::malloc(sizeof(T)));
|
||||
new (g_m_instance) T;
|
||||
}
|
||||
|
||||
return g_m_instance;
|
||||
}
|
||||
|
||||
KYTY_CLASS_NO_COPY(Singleton);
|
||||
|
||||
protected:
|
||||
Singleton();
|
||||
~Singleton();
|
||||
|
||||
private:
|
||||
static inline T* g_m_instance = nullptr;
|
||||
};
|
||||
|
||||
// template<class T> T* Singleton<T>::instance = 0;
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_SINGLETON_H_ */
|
||||
@@ -0,0 +1,344 @@
|
||||
#ifndef KYTY_COMMON_STRING_UTILS_H_
|
||||
#define KYTY_COMMON_STRING_UTILS_H_
|
||||
|
||||
#include "common/byteBuffer.h"
|
||||
#include "common/common.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <charconv>
|
||||
#include <codecvt>
|
||||
#include <filesystem>
|
||||
#include <fmt/format.h>
|
||||
#include <locale>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#include <vector>
|
||||
|
||||
namespace Common {
|
||||
|
||||
constexpr uint32_t FIND_INVALID_INDEX = static_cast<uint32_t>(-1);
|
||||
|
||||
inline std::string PathToString(const std::filesystem::path& path) {
|
||||
#if defined(__cpp_char8_t)
|
||||
auto u8 = path.u8string();
|
||||
return {u8.begin(), u8.end()};
|
||||
#else
|
||||
return path.u8string();
|
||||
#endif
|
||||
}
|
||||
|
||||
inline std::string PathToGenericString(const std::filesystem::path& path) {
|
||||
#if defined(__cpp_char8_t)
|
||||
auto u8 = path.generic_u8string();
|
||||
return {u8.begin(), u8.end()};
|
||||
#else
|
||||
return path.generic_u8string();
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool IsSpace(char c) {
|
||||
return std::isspace(static_cast<unsigned char>(c)) != 0;
|
||||
}
|
||||
|
||||
inline bool EqualNoCase(std::string_view lhs, std::string_view rhs) {
|
||||
if (lhs.size() != rhs.size()) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < lhs.size(); i++) {
|
||||
auto l = static_cast<unsigned char>(lhs[i]);
|
||||
auto r = static_cast<unsigned char>(rhs[i]);
|
||||
if (std::tolower(l) != std::tolower(r)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline std::string ToLower(std::string_view text) {
|
||||
std::string ret(text);
|
||||
std::transform(ret.begin(), ret.end(), ret.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline bool ContainsStr(std::string_view text, std::string_view needle) {
|
||||
return text.find(needle) != std::string_view::npos;
|
||||
}
|
||||
|
||||
inline bool StartsWith(std::string_view text, std::string_view prefix) {
|
||||
return text.starts_with(prefix);
|
||||
}
|
||||
|
||||
inline bool StartsWith(std::string_view text, char prefix) {
|
||||
return !text.empty() && text.front() == prefix;
|
||||
}
|
||||
|
||||
inline bool EndsWith(std::string_view text, std::string_view suffix) {
|
||||
return text.ends_with(suffix);
|
||||
}
|
||||
|
||||
inline bool EndsWith(std::string_view text, char suffix) {
|
||||
return !text.empty() && text.back() == suffix;
|
||||
}
|
||||
|
||||
inline bool ContainsChar(std::string_view text, char needle) {
|
||||
return text.find(needle) != std::string_view::npos;
|
||||
}
|
||||
|
||||
inline bool IndexValid(std::string_view text, uint32_t index) {
|
||||
return index < text.size();
|
||||
}
|
||||
|
||||
inline uint32_t FindIndex(std::string_view text, std::string_view needle, size_t from = 0) {
|
||||
const auto pos = text.find(needle, from);
|
||||
return pos == std::string_view::npos ? FIND_INVALID_INDEX : static_cast<uint32_t>(pos);
|
||||
}
|
||||
|
||||
inline uint32_t FindIndex(std::string_view text, char needle, size_t from = 0) {
|
||||
const auto pos = text.find(needle, from);
|
||||
return pos == std::string_view::npos ? FIND_INVALID_INDEX : static_cast<uint32_t>(pos);
|
||||
}
|
||||
|
||||
inline uint32_t FindLastIndex(std::string_view text, std::string_view needle,
|
||||
size_t from = FIND_INVALID_INDEX) {
|
||||
if (needle.empty()) {
|
||||
return 0;
|
||||
}
|
||||
if (from == FIND_INVALID_INDEX || from > text.size()) {
|
||||
from = text.size();
|
||||
}
|
||||
const auto pos = text.rfind(needle, from);
|
||||
return pos == std::string_view::npos ? FIND_INVALID_INDEX : static_cast<uint32_t>(pos);
|
||||
}
|
||||
|
||||
inline uint32_t FindLastIndex(std::string_view text, char needle,
|
||||
size_t from = FIND_INVALID_INDEX) {
|
||||
if (from == FIND_INVALID_INDEX || from > text.size()) {
|
||||
from = text.size();
|
||||
}
|
||||
const auto pos = text.rfind(needle, from);
|
||||
return pos == std::string_view::npos ? FIND_INVALID_INDEX : static_cast<uint32_t>(pos);
|
||||
}
|
||||
|
||||
inline std::string Mid(std::string_view text, size_t first, size_t count = std::string_view::npos) {
|
||||
if (first >= text.size()) {
|
||||
return {};
|
||||
}
|
||||
return std::string(text.substr(first, count));
|
||||
}
|
||||
|
||||
inline std::string Left(std::string_view text, size_t count) {
|
||||
return Mid(text, 0, count);
|
||||
}
|
||||
|
||||
inline std::string Right(std::string_view text, size_t count) {
|
||||
if (count >= text.size()) {
|
||||
return std::string(text);
|
||||
}
|
||||
return Mid(text, text.size() - count, count);
|
||||
}
|
||||
|
||||
inline std::string RemoveLast(std::string_view text, size_t count) {
|
||||
if (count >= text.size()) {
|
||||
return {};
|
||||
}
|
||||
return Left(text, text.size() - count);
|
||||
}
|
||||
|
||||
inline std::string RemoveFirst(std::string_view text, size_t count) {
|
||||
if (count >= text.size()) {
|
||||
return {};
|
||||
}
|
||||
return Right(text, text.size() - count);
|
||||
}
|
||||
|
||||
inline std::string TrimRight(std::string_view text) {
|
||||
size_t end = text.size();
|
||||
while (end > 0 && IsSpace(text[end - 1])) {
|
||||
end--;
|
||||
}
|
||||
return std::string(text.substr(0, end));
|
||||
}
|
||||
|
||||
inline std::string TrimLeft(std::string_view text) {
|
||||
size_t first = 0;
|
||||
while (first < text.size() && IsSpace(text[first])) {
|
||||
first++;
|
||||
}
|
||||
return std::string(text.substr(first));
|
||||
}
|
||||
|
||||
inline std::string Trim(std::string_view text) {
|
||||
return TrimRight(TrimLeft(text));
|
||||
}
|
||||
|
||||
inline std::string ReplaceChar(std::string_view text, char old_char, char new_char) {
|
||||
std::string ret(text);
|
||||
std::replace(ret.begin(), ret.end(), old_char, new_char);
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline std::string ReplaceStr(std::string text, std::string_view old_str,
|
||||
std::string_view new_str) {
|
||||
if (old_str.empty()) {
|
||||
return text;
|
||||
}
|
||||
|
||||
size_t pos = 0;
|
||||
while ((pos = text.find(old_str, pos)) != std::string::npos) {
|
||||
text.replace(pos, old_str.size(), new_str);
|
||||
pos += new_str.size();
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
inline std::string DirectoryWithoutFilename(std::string_view text) {
|
||||
const auto pos = text.find_last_of('/');
|
||||
return pos == std::string_view::npos ? std::string() : std::string(text.substr(0, pos + 1));
|
||||
}
|
||||
|
||||
inline std::string FilenameWithoutDirectory(std::string_view text) {
|
||||
const auto pos = text.find_last_of('/');
|
||||
return pos == std::string_view::npos ? std::string(text) : std::string(text.substr(pos + 1));
|
||||
}
|
||||
|
||||
inline std::string FilenameWithoutExtension(std::string_view text) {
|
||||
const auto pos = text.find_last_of('.');
|
||||
return pos == std::string_view::npos ? std::string(text) : std::string(text.substr(0, pos));
|
||||
}
|
||||
|
||||
inline std::string ExtensionWithoutFilename(std::string_view text) {
|
||||
const auto pos = text.find_last_of('.');
|
||||
return pos == std::string_view::npos ? std::string() : std::string(text.substr(pos));
|
||||
}
|
||||
|
||||
inline std::string FixFilenameSlash(std::string_view text) {
|
||||
return ReplaceChar(text, '\\', '/');
|
||||
}
|
||||
|
||||
inline std::string FixDirectorySlash(std::string_view text) {
|
||||
auto ret = FixFilenameSlash(text);
|
||||
if (!ret.ends_with('/')) {
|
||||
ret += '/';
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline std::vector<std::string> Split(std::string_view text, std::string_view sep,
|
||||
bool keep_empty = false) {
|
||||
std::vector<std::string> ret;
|
||||
if (sep.empty()) {
|
||||
for (char c: text) {
|
||||
ret.emplace_back(1, c);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t start = 0;
|
||||
for (;;) {
|
||||
const auto pos = text.find(sep, start);
|
||||
if (pos == std::string_view::npos) {
|
||||
break;
|
||||
}
|
||||
if (keep_empty || pos != start) {
|
||||
ret.emplace_back(text.substr(start, pos - start));
|
||||
}
|
||||
start = pos + sep.size();
|
||||
}
|
||||
if (keep_empty || start != text.size()) {
|
||||
ret.emplace_back(text.substr(start));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline std::vector<std::string> Split(std::string_view text, char sep, bool keep_empty = false) {
|
||||
return Split(text, std::string_view(&sep, 1), keep_empty);
|
||||
}
|
||||
|
||||
inline std::string Concat(const std::vector<std::string>& list, std::string_view sep) {
|
||||
std::string ret;
|
||||
for (size_t i = 0; i < list.size(); i++) {
|
||||
if (i != 0) {
|
||||
ret += sep;
|
||||
}
|
||||
ret += list[i];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline std::string Concat(const std::vector<std::string>& list, char sep) {
|
||||
return Concat(list, std::string_view(&sep, 1));
|
||||
}
|
||||
|
||||
inline uint32_t ToUint32(std::string_view text, int base = 10) {
|
||||
uint32_t value = 0;
|
||||
std::from_chars(text.data(), text.data() + text.size(), value, base);
|
||||
return value;
|
||||
}
|
||||
|
||||
inline uint64_t ToUint64(std::string_view text, int base = 10) {
|
||||
uint64_t value = 0;
|
||||
std::from_chars(text.data(), text.data() + text.size(), value, base);
|
||||
return value;
|
||||
}
|
||||
|
||||
inline int32_t ToInt32(std::string_view text, int base = 10) {
|
||||
int32_t value = 0;
|
||||
std::from_chars(text.data(), text.data() + text.size(), value, base);
|
||||
return value;
|
||||
}
|
||||
|
||||
inline int64_t ToInt64(std::string_view text, int base = 10) {
|
||||
int64_t value = 0;
|
||||
std::from_chars(text.data(), text.data() + text.size(), value, base);
|
||||
return value;
|
||||
}
|
||||
|
||||
inline double ToDouble(std::string_view text) {
|
||||
return std::stod(std::string(text));
|
||||
}
|
||||
|
||||
inline float ToFloat(std::string_view text) {
|
||||
return std::stof(std::string(text));
|
||||
}
|
||||
|
||||
inline std::string SafeCsv(std::string_view text) {
|
||||
const bool add_space =
|
||||
!text.empty() && (text.front() == '+' || text.front() == '=' || text.front() == '-');
|
||||
if (text.find('"') != std::string_view::npos || text.find(';') != std::string_view::npos ||
|
||||
text.find('+') != std::string_view::npos || text.find('=') != std::string_view::npos ||
|
||||
text.find('-') != std::string_view::npos) {
|
||||
return "\"" + std::string(add_space ? " " : "") +
|
||||
ReplaceStr(std::string(text), "\"", "\"\"") + "\"";
|
||||
}
|
||||
return std::string(text);
|
||||
}
|
||||
|
||||
inline std::string Utf16ToUtf8(const char16_t* utf16) {
|
||||
std::u16string input(utf16);
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
|
||||
return convert.to_bytes(input);
|
||||
}
|
||||
|
||||
inline ByteBuffer HexToBin(std::string_view text) {
|
||||
ByteBuffer ret;
|
||||
for (size_t i = 0; i + 1 < text.size(); i += 2) {
|
||||
ret.Add(static_cast<Byte>((ToUint32(text.substr(i, 2), 16) & 0xffu)));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline std::string HexFromBin(const ByteBuffer& bin) {
|
||||
std::string ret;
|
||||
ret.reserve(bin.Size() * 2);
|
||||
for (uint32_t i = 0; i < bin.Size(); i++) {
|
||||
ret += fmt::format("{:02X}", std::to_integer<uint8_t>(bin.At(i)));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_STRING_UTILS_H_ */
|
||||
@@ -0,0 +1,232 @@
|
||||
#include "common/subsystems.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace Common {
|
||||
|
||||
class SubsystemPrivate {
|
||||
public:
|
||||
SubsystemPrivate() = default;
|
||||
virtual ~SubsystemPrivate() = default;
|
||||
|
||||
KYTY_CLASS_NO_COPY(SubsystemPrivate);
|
||||
|
||||
bool failed {false};
|
||||
std::string fail_msg;
|
||||
};
|
||||
|
||||
class SubsystemsListPrivate {
|
||||
public:
|
||||
explicit SubsystemsListPrivate(SubsystemsList& p): parent(p) {}
|
||||
|
||||
virtual ~SubsystemsListPrivate() = default;
|
||||
|
||||
void SetArgs(int argc, char** argv) {
|
||||
this->m_argc = argc;
|
||||
this->m_argv = argv;
|
||||
}
|
||||
|
||||
void Add(Subsystem* s, std::initializer_list<Subsystem*> deps) {
|
||||
EXIT_IF(!s);
|
||||
|
||||
const char* name = s->Id();
|
||||
|
||||
EXIT_IF(FindByName(name) != nullptr);
|
||||
|
||||
auto nl = std::make_unique<SubsListStruct>();
|
||||
|
||||
nl->s = s;
|
||||
nl->name = name;
|
||||
nl->deps = nullptr;
|
||||
nl->next = std::move(list);
|
||||
nl->prev_init = nullptr;
|
||||
|
||||
auto* node = nl.get();
|
||||
list = std::move(nl);
|
||||
|
||||
for (auto* dep: deps) {
|
||||
const char* str = dep->Id();
|
||||
|
||||
auto l = std::make_unique<DepsListStruct>();
|
||||
l->dep_name = str;
|
||||
l->next = std::move(node->deps);
|
||||
|
||||
node->deps = std::move(l);
|
||||
}
|
||||
|
||||
node->initialized = false;
|
||||
}
|
||||
|
||||
bool InitAll(bool print_msg) {
|
||||
while (SubsListStruct* n = FindNextToInitialize()) {
|
||||
n->s->Init(&parent);
|
||||
|
||||
if (n->s->m_p->failed) {
|
||||
fail_msg = n->s->m_p->fail_msg.c_str();
|
||||
fail_name = n->name;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (print_msg) {
|
||||
printf("Initialized: %s\n", n->name);
|
||||
}
|
||||
|
||||
n->initialized = true;
|
||||
|
||||
SubsListStruct* last = last_init;
|
||||
last_init = n;
|
||||
n->prev_init = last;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DestroyAll(bool print_msg) {
|
||||
for (SubsListStruct* n = last_init; n != nullptr; n = n->prev_init) {
|
||||
n->s->Destroy(&parent);
|
||||
n->initialized = false;
|
||||
|
||||
if (print_msg) {
|
||||
printf("Destroyed: %s\n", n->name);
|
||||
}
|
||||
}
|
||||
|
||||
last_init = nullptr;
|
||||
}
|
||||
|
||||
void ShutdownAll() {
|
||||
for (SubsListStruct* n = last_init; n != nullptr; n = n->prev_init) {
|
||||
n->s->UnexpectedShutdown(&parent);
|
||||
n->initialized = false;
|
||||
}
|
||||
|
||||
last_init = nullptr;
|
||||
}
|
||||
|
||||
struct DepsListStruct {
|
||||
const char* dep_name;
|
||||
std::unique_ptr<DepsListStruct> next;
|
||||
};
|
||||
|
||||
struct SubsListStruct {
|
||||
Subsystem* s;
|
||||
const char* name;
|
||||
std::unique_ptr<DepsListStruct> deps;
|
||||
std::unique_ptr<SubsListStruct> next;
|
||||
SubsListStruct* prev_init;
|
||||
bool initialized;
|
||||
};
|
||||
|
||||
[[nodiscard]] SubsListStruct* FindByName(const char* name) const {
|
||||
for (SubsListStruct* n = list.get(); n != nullptr; n = n->next.get()) {
|
||||
if (std::strcmp(n->name, name) == 0) {
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] SubsListStruct* FindNextToInitialize() const {
|
||||
for (SubsListStruct* n = list.get(); n != nullptr; n = n->next.get()) {
|
||||
if (n->initialized) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DepsListStruct* d = n->deps.get();
|
||||
for (; d != nullptr; d = d->next.get()) {
|
||||
SubsListStruct* s = FindByName(d->dep_name);
|
||||
if ((s == nullptr) || !s->initialized) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (d == nullptr) {
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
KYTY_CLASS_NO_COPY(SubsystemsListPrivate);
|
||||
|
||||
std::unique_ptr<SubsListStruct> list;
|
||||
SubsListStruct* last_init = nullptr;
|
||||
int m_argc = 0;
|
||||
char** m_argv = nullptr;
|
||||
const char* fail_msg = nullptr;
|
||||
const char* fail_name = nullptr;
|
||||
SubsystemsList& parent;
|
||||
};
|
||||
|
||||
SubsystemsList::SubsystemsList(): m_p(std::make_unique<SubsystemsListPrivate>(*this)) {}
|
||||
|
||||
SubsystemsList::~SubsystemsList() = default;
|
||||
|
||||
void SubsystemsList::Add(Subsystem* s, std::initializer_list<Subsystem*> deps) {
|
||||
m_p->Add(s, deps);
|
||||
}
|
||||
|
||||
bool SubsystemsList::InitAll(bool print_msg) {
|
||||
return m_p->InitAll(print_msg);
|
||||
}
|
||||
|
||||
void SubsystemsList::DestroyAll(bool print_msg) {
|
||||
m_p->DestroyAll(print_msg);
|
||||
}
|
||||
|
||||
int* SubsystemsList::GetArgc() {
|
||||
return &m_p->m_argc;
|
||||
}
|
||||
|
||||
char** SubsystemsList::GetArgv() {
|
||||
return m_p->m_argv;
|
||||
}
|
||||
|
||||
Subsystem::Subsystem(): m_p(std::make_unique<SubsystemPrivate>()) {}
|
||||
|
||||
Subsystem::~Subsystem() = default;
|
||||
|
||||
void Subsystem::Fail(const char* format, ...) {
|
||||
va_list args {};
|
||||
va_start(args, format);
|
||||
|
||||
va_list args_copy {};
|
||||
va_copy(args_copy, args);
|
||||
int len = std::vsnprintf(nullptr, 0, format, args_copy);
|
||||
va_end(args_copy);
|
||||
|
||||
if (len > 0) {
|
||||
std::string msg(static_cast<size_t>(len) + 1, '\0');
|
||||
std::vsnprintf(msg.data(), msg.size(), format, args);
|
||||
m_p->fail_msg = msg.c_str();
|
||||
m_p->failed = true;
|
||||
}
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
const char* SubsystemsList::GetFailName() const {
|
||||
return m_p->fail_name;
|
||||
}
|
||||
|
||||
const char* SubsystemsList::GetFailMsg() const {
|
||||
return m_p->fail_msg;
|
||||
}
|
||||
|
||||
void SubsystemsList::SetArgs(int argc, char* argv[]) {
|
||||
m_p->SetArgs(argc, argv);
|
||||
}
|
||||
|
||||
void SubsystemsList::ShutdownAll() {
|
||||
m_p->ShutdownAll();
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,91 @@
|
||||
#ifndef KYTY_COMMON_SUBSYSTEMS_H_
|
||||
#define KYTY_COMMON_SUBSYSTEMS_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/singleton.h"
|
||||
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
|
||||
namespace Common {
|
||||
|
||||
class Subsystem;
|
||||
class SubsystemsListPrivate;
|
||||
class SubsystemPrivate;
|
||||
|
||||
class SubsystemsList {
|
||||
public:
|
||||
SubsystemsList();
|
||||
virtual ~SubsystemsList();
|
||||
void SetArgs(int argc, char* argv[]);
|
||||
|
||||
// void Add(Subsystem* s, const char* name, ...);
|
||||
void Add(Subsystem* s, std::initializer_list<Subsystem*> deps);
|
||||
|
||||
bool InitAll(bool print_msg = false);
|
||||
void DestroyAll(bool print_msg = false);
|
||||
|
||||
int* GetArgc();
|
||||
char** GetArgv();
|
||||
|
||||
[[nodiscard]] const char* GetFailName() const;
|
||||
[[nodiscard]] const char* GetFailMsg() const;
|
||||
|
||||
void ShutdownAll();
|
||||
|
||||
static SubsystemsList* Instance() { return Common::Singleton<SubsystemsList>::Instance(); }
|
||||
|
||||
KYTY_CLASS_NO_COPY(SubsystemsList);
|
||||
|
||||
private:
|
||||
std::unique_ptr<SubsystemsListPrivate> m_p;
|
||||
};
|
||||
|
||||
using SubsystemsListSingleton = Common::Singleton<SubsystemsList>;
|
||||
|
||||
class Subsystem {
|
||||
public:
|
||||
Subsystem();
|
||||
virtual ~Subsystem();
|
||||
|
||||
virtual const char* Id() = 0;
|
||||
virtual void Init(SubsystemsList* parent) = 0;
|
||||
virtual void Destroy(SubsystemsList* parent) = 0;
|
||||
virtual void UnexpectedShutdown(SubsystemsList* parent) = 0;
|
||||
|
||||
friend class SubsystemsListPrivate;
|
||||
|
||||
KYTY_CLASS_NO_COPY(Subsystem);
|
||||
|
||||
protected:
|
||||
void Fail(const char* format, ...) KYTY_FORMAT_PRINTF(2, 3);
|
||||
|
||||
private:
|
||||
std::unique_ptr<SubsystemPrivate> m_p;
|
||||
};
|
||||
|
||||
#define KYTY_SUBSYSTEM_DEFINE(s) \
|
||||
class s##Subsystem: public Common::Subsystem { \
|
||||
public: \
|
||||
static Subsystem* Instance() { return Common::Singleton<s##Subsystem>::Instance(); } \
|
||||
const char* Id() { return #s; } \
|
||||
void Init(Common::SubsystemsList* parent); \
|
||||
void Destroy(Common::SubsystemsList* parent); \
|
||||
void UnexpectedShutdown(Common::SubsystemsList* parent); \
|
||||
}; \
|
||||
typedef Common::Singleton<s##Subsystem> s##SubsystemSingleton;
|
||||
|
||||
#define KYTY_SUBSYSTEM_INIT(s) \
|
||||
void s##Subsystem::Init([[maybe_unused]] Common::SubsystemsList* parent)
|
||||
#define KYTY_SUBSYSTEM_DESTROY(s) \
|
||||
void s##Subsystem::Destroy([[maybe_unused]] Common::SubsystemsList* parent)
|
||||
#define KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(s) \
|
||||
void s##Subsystem::UnexpectedShutdown([[maybe_unused]] Common::SubsystemsList* parent)
|
||||
|
||||
// #define KYTY_SUBSYSTEM_ADD(list, s, ...) list.push_back(s##SubsystemSingleton::Instance(), #s,
|
||||
// __VA_ARGS__); #define KYTY_SUBSYSTEM_ADD2(list, ...) list.Add2(s##SubsystemSingleton::Instance(),
|
||||
// __VA_ARGS__);
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_SUBSYSTEMS_H_ */
|
||||
@@ -0,0 +1,18 @@
|
||||
#include "common/systemInfo.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "cpuinfo.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
SystemInfo GetSystemInfo() {
|
||||
static const bool initialized = cpuinfo_initialize();
|
||||
EXIT_IF(!initialized);
|
||||
|
||||
const auto* package = cpuinfo_get_package(0);
|
||||
EXIT_IF(package == nullptr);
|
||||
|
||||
return {package->name};
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef KYTY_COMMON_SYSTEM_INFO_H_
|
||||
#define KYTY_COMMON_SYSTEM_INFO_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Common {
|
||||
|
||||
struct SystemInfo {
|
||||
std::string ProcessorName;
|
||||
};
|
||||
|
||||
[[nodiscard]] SystemInfo GetSystemInfo();
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_SYSTEM_INFO_H_ */
|
||||
@@ -0,0 +1,419 @@
|
||||
#include "common/threads.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono> // IWYU pragma: keep
|
||||
#include <condition_variable> // IWYU pragma: keep
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && KYTY_COMPILER == KYTY_COMPILER_CLANG
|
||||
#define KYTY_WIN_CS
|
||||
#endif
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#ifdef KYTY_WIN_CS
|
||||
#include <windows.h> // IWYU pragma: keep
|
||||
// IWYU pragma: no_include <winbase.h>
|
||||
constexpr DWORD KYTY_CS_SPIN_COUNT = 4000;
|
||||
|
||||
#ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION
|
||||
#define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x00000002
|
||||
#endif
|
||||
|
||||
static void SleepHighResolution100ns(uint64_t units_100ns) {
|
||||
if (units_100ns == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (units_100ns <= 10000) {
|
||||
LARGE_INTEGER frequency {};
|
||||
LARGE_INTEGER start {};
|
||||
if (QueryPerformanceFrequency(&frequency) != 0 && QueryPerformanceCounter(&start) != 0 &&
|
||||
frequency.QuadPart > 0) {
|
||||
const auto wait_ticks =
|
||||
static_cast<LONGLONG>((static_cast<long double>(units_100ns) *
|
||||
static_cast<long double>(frequency.QuadPart)) /
|
||||
10000000.0L);
|
||||
const auto deadline = start.QuadPart + std::max<LONGLONG>(wait_ticks, 1);
|
||||
LARGE_INTEGER now {};
|
||||
do {
|
||||
if (QueryPerformanceCounter(&now) == 0) {
|
||||
break;
|
||||
}
|
||||
YieldProcessor();
|
||||
} while (now.QuadPart < deadline);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
thread_local HANDLE timer = CreateWaitableTimerExW(
|
||||
nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
|
||||
if (timer == nullptr) {
|
||||
thread_local HANDLE fallback_timer = CreateWaitableTimerW(nullptr, TRUE, nullptr);
|
||||
timer = fallback_timer;
|
||||
}
|
||||
|
||||
if (timer != nullptr) {
|
||||
LARGE_INTEGER due_time {};
|
||||
due_time.QuadPart = -static_cast<LONGLONG>(units_100ns);
|
||||
if (SetWaitableTimerEx(timer, &due_time, 0, nullptr, nullptr, nullptr, 0) != 0) {
|
||||
WaitForSingleObject(timer, INFINITE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::nanoseconds(units_100ns * 100));
|
||||
}
|
||||
|
||||
// IWYU pragma: no_include <minwindef.h>
|
||||
// IWYU pragma: no_include <synchapi.h>
|
||||
// IWYU pragma: no_include <minwinbase.h>
|
||||
// IWYU pragma: no_include <__mutex_base>
|
||||
// IWYU pragma: no_include <__threading_support>
|
||||
// IWYU pragma: no_include <errhandlingapi.h>
|
||||
// IWYU pragma: no_include <winerror.h>
|
||||
|
||||
using InitializeConditionVariable_func_t = /*WINBASEAPI*/ VOID WINAPI (*)(PCONDITION_VARIABLE);
|
||||
using WakeConditionVariable_func_t = /*WINBASEAPI*/ VOID WINAPI (*)(PCONDITION_VARIABLE);
|
||||
using WakeAllConditionVariable_func_t = /*WINBASEAPI*/ VOID WINAPI (*)(PCONDITION_VARIABLE);
|
||||
using SleepConditionVariableCS_func_t = /*WINBASEAPI*/ BOOL WINAPI (*)(PCONDITION_VARIABLE,
|
||||
PCRITICAL_SECTION, DWORD);
|
||||
|
||||
static InitializeConditionVariable_func_t ResolveInitializeConditionVariable() {
|
||||
if (HMODULE h = GetModuleHandle("KernelBase"); h != nullptr) // @suppress("Invalid arguments")
|
||||
{
|
||||
return reinterpret_cast<InitializeConditionVariable_func_t>(
|
||||
GetProcAddress(h, "InitializeConditionVariable"));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
static WakeConditionVariable_func_t ResolveWakeConditionVariable() {
|
||||
if (HMODULE h = GetModuleHandle("KernelBase"); h != nullptr) // @suppress("Invalid arguments")
|
||||
{
|
||||
return reinterpret_cast<WakeConditionVariable_func_t>(
|
||||
GetProcAddress(h, "WakeConditionVariable"));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
static WakeAllConditionVariable_func_t ResolveWakeAllConditionVariable() {
|
||||
if (HMODULE h = GetModuleHandle("KernelBase"); h != nullptr) // @suppress("Invalid arguments")
|
||||
{
|
||||
return reinterpret_cast<WakeAllConditionVariable_func_t>(
|
||||
GetProcAddress(h, "WakeAllConditionVariable"));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
static SleepConditionVariableCS_func_t ResolveSleepConditionVariableCS() {
|
||||
if (HMODULE h = GetModuleHandle("KernelBase"); h != nullptr) // @suppress("Invalid arguments")
|
||||
{
|
||||
return reinterpret_cast<SleepConditionVariableCS_func_t>(
|
||||
GetProcAddress(h, "SleepConditionVariableCS"));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
using thread_id_t = std::thread::id;
|
||||
|
||||
struct MutexPrivate {
|
||||
#ifdef KYTY_WIN_CS
|
||||
MutexPrivate() { InitializeCriticalSectionAndSpinCount(&m_cs, KYTY_CS_SPIN_COUNT); }
|
||||
~MutexPrivate() { DeleteCriticalSection(&m_cs); }
|
||||
KYTY_CLASS_NO_COPY(MutexPrivate);
|
||||
CRITICAL_SECTION m_cs {};
|
||||
#else
|
||||
std::recursive_mutex m_mutex;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct CondVarPrivate {
|
||||
#ifdef KYTY_WIN_CS
|
||||
CondVarPrivate() {
|
||||
static auto func = ResolveInitializeConditionVariable();
|
||||
EXIT_NOT_IMPLEMENTED(func == nullptr);
|
||||
func(&m_cv);
|
||||
}
|
||||
~CondVarPrivate() = default;
|
||||
KYTY_CLASS_NO_COPY(CondVarPrivate);
|
||||
CONDITION_VARIABLE m_cv {};
|
||||
#else
|
||||
std::condition_variable_any m_cv;
|
||||
#endif
|
||||
};
|
||||
|
||||
static std::recursive_mutex g_cond_waiters_mutex;
|
||||
static std::vector<std::pair<int, CondVarPrivate*>> g_cond_waiters;
|
||||
static wait_poll_func_t g_cond_wait_poll_callback = nullptr;
|
||||
|
||||
static void WakeCondVar(CondVarPrivate* cond_var) {
|
||||
#ifdef KYTY_WIN_CS
|
||||
static auto func = ResolveWakeAllConditionVariable();
|
||||
EXIT_NOT_IMPLEMENTED(func == nullptr);
|
||||
func(&cond_var->m_cv);
|
||||
#else
|
||||
cond_var->m_cv.notify_all();
|
||||
#endif
|
||||
}
|
||||
|
||||
static void RegisterCondWaiter(CondVarPrivate* cond_var) {
|
||||
std::lock_guard lock(g_cond_waiters_mutex);
|
||||
g_cond_waiters.emplace_back(Thread::GetThreadIdUnique(), cond_var);
|
||||
}
|
||||
|
||||
static void UnregisterCondWaiter(CondVarPrivate* cond_var) {
|
||||
const auto thread_id = Thread::GetThreadIdUnique();
|
||||
std::lock_guard lock(g_cond_waiters_mutex);
|
||||
|
||||
const auto it = std::find_if(g_cond_waiters.begin(), g_cond_waiters.end(),
|
||||
[thread_id, cond_var](const auto& waiter) {
|
||||
return waiter.first == thread_id && waiter.second == cond_var;
|
||||
});
|
||||
if (it != g_cond_waiters.end()) {
|
||||
g_cond_waiters.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
struct ThreadPrivate {
|
||||
ThreadPrivate(thread_func_t f, void* a): func(f), arg(a), m_thread(&Run, this) {}
|
||||
|
||||
static void Run(ThreadPrivate* t) {
|
||||
t->unique_id = Thread::GetThreadIdUnique();
|
||||
t->started = true;
|
||||
t->func(t->arg);
|
||||
}
|
||||
|
||||
thread_func_t func;
|
||||
void* arg;
|
||||
std::atomic_bool finished = false;
|
||||
std::atomic_bool auto_delete = false;
|
||||
std::atomic_bool started = false;
|
||||
int unique_id = 0;
|
||||
std::thread m_thread;
|
||||
};
|
||||
|
||||
static thread_id_t g_main_thread;
|
||||
static int g_main_thread_int;
|
||||
static std::atomic<int> g_thread_counter = 0;
|
||||
|
||||
KYTY_SUBSYSTEM_INIT(Threads) {
|
||||
g_main_thread = std::this_thread::get_id();
|
||||
g_main_thread_int = Thread::GetThreadIdUnique();
|
||||
}
|
||||
|
||||
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Threads) {}
|
||||
|
||||
KYTY_SUBSYSTEM_DESTROY(Threads) {}
|
||||
|
||||
Thread::Thread(thread_func_t func, void* arg)
|
||||
: m_thread(std::make_unique<ThreadPrivate>(func, arg)) {
|
||||
while (!m_thread->started) {
|
||||
Common::Thread::SleepMicro(1000);
|
||||
}
|
||||
}
|
||||
|
||||
Thread::~Thread() {
|
||||
EXIT_IF(!m_thread->finished && !m_thread->auto_delete);
|
||||
|
||||
m_thread.reset();
|
||||
}
|
||||
|
||||
void Thread::Join() {
|
||||
EXIT_IF(m_thread->finished || m_thread->auto_delete);
|
||||
|
||||
m_thread->m_thread.join();
|
||||
|
||||
m_thread->finished = true;
|
||||
}
|
||||
|
||||
void Thread::Detach() {
|
||||
EXIT_IF(m_thread->finished || m_thread->auto_delete);
|
||||
|
||||
m_thread->auto_delete = true;
|
||||
m_thread->m_thread.detach();
|
||||
}
|
||||
|
||||
void Thread::Sleep(uint32_t millis) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(millis));
|
||||
}
|
||||
|
||||
void Thread::SleepMicro(uint32_t micros) {
|
||||
#ifdef KYTY_WIN_CS
|
||||
SleepHighResolution100ns(static_cast<uint64_t>(micros) * 10);
|
||||
#else
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(micros));
|
||||
#endif
|
||||
}
|
||||
|
||||
void Thread::SleepNano(uint64_t nanos) {
|
||||
#ifdef KYTY_WIN_CS
|
||||
SleepHighResolution100ns((nanos + 99) / 100);
|
||||
#else
|
||||
std::this_thread::sleep_for(std::chrono::nanoseconds(nanos));
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Thread::IsMainThread() {
|
||||
return g_main_thread == std::this_thread::get_id();
|
||||
}
|
||||
|
||||
std::string Thread::GetId() const {
|
||||
std::stringstream ss;
|
||||
ss << m_thread->m_thread.get_id();
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
int Thread::GetUniqueId() const {
|
||||
return m_thread->unique_id;
|
||||
}
|
||||
|
||||
std::string Thread::GetThreadId() {
|
||||
std::stringstream ss;
|
||||
ss << std::this_thread::get_id();
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
Mutex::Mutex(): m_mutex(std::make_unique<MutexPrivate>()) {}
|
||||
|
||||
Mutex::~Mutex() {
|
||||
m_mutex.reset();
|
||||
}
|
||||
|
||||
void Mutex::Lock() {
|
||||
#ifdef KYTY_WIN_CS
|
||||
EnterCriticalSection(&m_mutex->m_cs);
|
||||
#else
|
||||
m_mutex->m_mutex.lock();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Mutex::Unlock() {
|
||||
#ifdef KYTY_WIN_CS
|
||||
LeaveCriticalSection(&m_mutex->m_cs);
|
||||
#else
|
||||
m_mutex->m_mutex.unlock();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Mutex::TryLock() {
|
||||
#ifdef KYTY_WIN_CS
|
||||
return (TryEnterCriticalSection(&m_mutex->m_cs) != 0);
|
||||
#else
|
||||
return m_mutex->m_mutex.try_lock();
|
||||
#endif
|
||||
}
|
||||
|
||||
CondVar::CondVar(): m_cond_var(std::make_unique<CondVarPrivate>()) {}
|
||||
|
||||
CondVar::~CondVar() {
|
||||
m_cond_var.reset();
|
||||
}
|
||||
|
||||
void CondVar::Wait(Mutex* mutex) {
|
||||
RegisterCondWaiter(m_cond_var.get());
|
||||
#ifndef KYTY_WIN_CS
|
||||
std::unique_lock<std::recursive_mutex> cpp_lock(mutex->m_mutex->m_mutex, std::adopt_lock_t());
|
||||
#endif
|
||||
auto poll_callback = [&] {
|
||||
auto* callback = g_cond_wait_poll_callback;
|
||||
if (callback == nullptr) {
|
||||
return;
|
||||
}
|
||||
#if defined(KYTY_WIN_CS)
|
||||
LeaveCriticalSection(&mutex->m_mutex->m_cs);
|
||||
callback();
|
||||
EnterCriticalSection(&mutex->m_mutex->m_cs);
|
||||
#else
|
||||
cpp_lock.unlock();
|
||||
callback();
|
||||
cpp_lock.lock();
|
||||
#endif
|
||||
};
|
||||
#ifdef KYTY_WIN_CS
|
||||
static auto func = ResolveSleepConditionVariableCS();
|
||||
EXIT_NOT_IMPLEMENTED(func == nullptr);
|
||||
if (g_cond_wait_poll_callback == nullptr) {
|
||||
func(&m_cond_var->m_cv, &mutex->m_mutex->m_cs, INFINITE);
|
||||
} else {
|
||||
if (func(&m_cond_var->m_cv, &mutex->m_mutex->m_cs, 10) == 0 &&
|
||||
GetLastError() == ERROR_TIMEOUT) {
|
||||
poll_callback();
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (g_cond_wait_poll_callback == nullptr) {
|
||||
m_cond_var->m_cv.wait(cpp_lock);
|
||||
} else {
|
||||
if (m_cond_var->m_cv.wait_for(cpp_lock, std::chrono::microseconds(10000)) ==
|
||||
std::cv_status::timeout) {
|
||||
poll_callback();
|
||||
}
|
||||
}
|
||||
cpp_lock.release();
|
||||
#endif
|
||||
UnregisterCondWaiter(m_cond_var.get());
|
||||
}
|
||||
|
||||
void CondVar::SetWaitPollCallback(wait_poll_func_t callback) {
|
||||
g_cond_wait_poll_callback = callback;
|
||||
}
|
||||
|
||||
bool CondVar::WaitFor(Mutex* mutex, uint32_t micros) {
|
||||
bool ok = false;
|
||||
RegisterCondWaiter(m_cond_var.get());
|
||||
#ifndef KYTY_WIN_CS
|
||||
std::unique_lock<std::recursive_mutex> cpp_lock(mutex->m_mutex->m_mutex, std::adopt_lock_t());
|
||||
#endif
|
||||
#ifdef KYTY_WIN_CS
|
||||
static auto func = ResolveSleepConditionVariableCS();
|
||||
EXIT_NOT_IMPLEMENTED(func == nullptr);
|
||||
ok = !(func(&m_cond_var->m_cv, &mutex->m_mutex->m_cs, (micros < 1000 ? 1 : micros / 1000)) ==
|
||||
0 &&
|
||||
GetLastError() == ERROR_TIMEOUT);
|
||||
#else
|
||||
ok = (m_cond_var->m_cv.wait_for(cpp_lock, std::chrono::microseconds(micros)) ==
|
||||
std::cv_status::no_timeout);
|
||||
cpp_lock.release();
|
||||
#endif
|
||||
UnregisterCondWaiter(m_cond_var.get());
|
||||
return ok;
|
||||
}
|
||||
|
||||
void CondVar::Signal() {
|
||||
#ifdef KYTY_WIN_CS
|
||||
static auto func = ResolveWakeConditionVariable();
|
||||
EXIT_NOT_IMPLEMENTED(func == nullptr);
|
||||
func(&m_cond_var->m_cv);
|
||||
#else
|
||||
m_cond_var->m_cv.notify_one();
|
||||
#endif
|
||||
}
|
||||
|
||||
void CondVar::SignalAll() {
|
||||
WakeCondVar(m_cond_var.get());
|
||||
}
|
||||
|
||||
void CondVar::SignalThread(int thread_id) {
|
||||
std::lock_guard lock(g_cond_waiters_mutex);
|
||||
for (const auto& waiter: g_cond_waiters) {
|
||||
if (waiter.first == thread_id) {
|
||||
WakeCondVar(waiter.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Thread::GetThreadIdUnique() {
|
||||
static thread_local int tid = ++g_thread_counter;
|
||||
return tid;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,107 @@
|
||||
#ifndef KYTY_COMMON_THREADS_H_
|
||||
#define KYTY_COMMON_THREADS_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/subsystems.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace Common {
|
||||
|
||||
KYTY_SUBSYSTEM_DEFINE(Threads);
|
||||
|
||||
using thread_func_t = void (*)(void*);
|
||||
using wait_poll_func_t = void (*)();
|
||||
|
||||
struct ThreadPrivate;
|
||||
struct MutexPrivate;
|
||||
struct CondVarPrivate;
|
||||
|
||||
class Thread {
|
||||
public:
|
||||
Thread(thread_func_t func, void* arg);
|
||||
~Thread();
|
||||
|
||||
void Join();
|
||||
void Detach();
|
||||
|
||||
// Once a thread has finished, the id may be reused by another thread.
|
||||
[[nodiscard]] std::string GetId() const;
|
||||
|
||||
// The id is unique and can't be reused by another thread.
|
||||
[[nodiscard]] int GetUniqueId() const;
|
||||
|
||||
static void Sleep(uint32_t millis);
|
||||
static void SleepMicro(uint32_t micros);
|
||||
static void SleepNano(uint64_t nanos);
|
||||
static bool IsMainThread();
|
||||
|
||||
// Get current thread id
|
||||
// Once a thread has finished, the id may be reused by another thread.
|
||||
static std::string GetThreadId();
|
||||
|
||||
// Get current thread id
|
||||
// The id is unique and can't be reused by another thread.
|
||||
static int GetThreadIdUnique();
|
||||
|
||||
KYTY_CLASS_NO_COPY(Thread);
|
||||
|
||||
private:
|
||||
std::unique_ptr<ThreadPrivate> m_thread;
|
||||
};
|
||||
|
||||
class Mutex {
|
||||
public:
|
||||
Mutex();
|
||||
~Mutex();
|
||||
|
||||
void Lock();
|
||||
void Unlock();
|
||||
bool TryLock();
|
||||
|
||||
friend class CondVar;
|
||||
|
||||
KYTY_CLASS_NO_COPY(Mutex);
|
||||
|
||||
private:
|
||||
std::unique_ptr<MutexPrivate> m_mutex;
|
||||
};
|
||||
|
||||
class CondVar {
|
||||
public:
|
||||
CondVar();
|
||||
~CondVar();
|
||||
|
||||
void Wait(Mutex* mutex);
|
||||
bool WaitFor(Mutex* mutex, uint32_t micros);
|
||||
void Signal();
|
||||
void SignalAll();
|
||||
|
||||
static void SignalThread(int thread_id);
|
||||
static void SetWaitPollCallback(wait_poll_func_t callback);
|
||||
|
||||
KYTY_CLASS_NO_COPY(CondVar);
|
||||
|
||||
private:
|
||||
std::unique_ptr<CondVarPrivate> m_cond_var;
|
||||
};
|
||||
|
||||
class LockGuard {
|
||||
public:
|
||||
using mutex_type = Mutex;
|
||||
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
explicit LockGuard(mutex_type& m): m_mutex(m) { m_mutex.Lock(); }
|
||||
|
||||
~LockGuard() { m_mutex.Unlock(); }
|
||||
|
||||
KYTY_CLASS_NO_COPY(LockGuard);
|
||||
|
||||
private:
|
||||
mutex_type& m_mutex;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_THREADS_H_ */
|
||||
@@ -0,0 +1,94 @@
|
||||
#include "common/timer.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/platform/sysTimer.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
Timer::Timer() noexcept {
|
||||
SysQueryPerformanceFrequency(&m_Frequency);
|
||||
}
|
||||
|
||||
void Timer::Start() {
|
||||
SysQueryPerformanceCounter(&m_StartTime);
|
||||
m_is_paused = false;
|
||||
}
|
||||
|
||||
void Timer::Pause() {
|
||||
EXIT_IF(m_is_paused);
|
||||
|
||||
SysQueryPerformanceCounter(&m_PauseTime);
|
||||
m_is_paused = true;
|
||||
}
|
||||
|
||||
void Timer::Resume() {
|
||||
EXIT_IF(!m_is_paused);
|
||||
|
||||
uint64_t current_time = 0;
|
||||
SysQueryPerformanceCounter(¤t_time);
|
||||
|
||||
m_StartTime += current_time - m_PauseTime;
|
||||
|
||||
m_is_paused = false;
|
||||
}
|
||||
|
||||
bool Timer::IsPaused() const {
|
||||
return m_is_paused;
|
||||
}
|
||||
|
||||
// return time in milliseconds
|
||||
double Timer::GetTimeMs() const {
|
||||
if (m_is_paused) {
|
||||
return 1000.0 * (static_cast<double>(m_PauseTime - m_StartTime)) /
|
||||
static_cast<double>(m_Frequency);
|
||||
}
|
||||
|
||||
uint64_t current_time = 0;
|
||||
SysQueryPerformanceCounter(¤t_time);
|
||||
|
||||
return 1000.0 * (static_cast<double>(current_time - m_StartTime)) /
|
||||
static_cast<double>(m_Frequency);
|
||||
}
|
||||
|
||||
// return time in seconds
|
||||
double Timer::GetTimeS() const {
|
||||
if (m_is_paused) {
|
||||
return (static_cast<double>(m_PauseTime - m_StartTime)) / static_cast<double>(m_Frequency);
|
||||
}
|
||||
|
||||
uint64_t current_time = 0;
|
||||
SysQueryPerformanceCounter(¤t_time);
|
||||
|
||||
return (static_cast<double>(current_time - m_StartTime)) / static_cast<double>(m_Frequency);
|
||||
}
|
||||
|
||||
// return time in ticks
|
||||
uint64_t Timer::GetTicks() const {
|
||||
if (m_is_paused) {
|
||||
return (m_PauseTime - m_StartTime);
|
||||
}
|
||||
|
||||
uint64_t current_time = 0;
|
||||
SysQueryPerformanceCounter(¤t_time);
|
||||
|
||||
return (current_time - m_StartTime);
|
||||
}
|
||||
|
||||
// return ticks frequency
|
||||
uint64_t Timer::GetFrequency() const {
|
||||
return m_Frequency;
|
||||
}
|
||||
|
||||
uint64_t Timer::QueryPerformanceFrequency() {
|
||||
uint64_t ret = 0;
|
||||
SysQueryPerformanceFrequency(&ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint64_t Timer::QueryPerformanceCounter() {
|
||||
uint64_t ret = 0;
|
||||
SysQueryPerformanceCounter(&ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,47 @@
|
||||
#ifndef KYTY_COMMON_TIMER_H_
|
||||
#define KYTY_COMMON_TIMER_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
class Timer final {
|
||||
public:
|
||||
Timer() noexcept;
|
||||
~Timer() = default;
|
||||
|
||||
void Start();
|
||||
|
||||
void Pause();
|
||||
|
||||
void Resume();
|
||||
|
||||
[[nodiscard]] bool IsPaused() const;
|
||||
|
||||
// return time in milliseconds
|
||||
[[nodiscard]] double GetTimeMs() const;
|
||||
|
||||
// return time in seconds
|
||||
[[nodiscard]] double GetTimeS() const;
|
||||
|
||||
// return time in ticks
|
||||
[[nodiscard]] uint64_t GetTicks() const;
|
||||
|
||||
// return ticks frequency
|
||||
[[nodiscard]] uint64_t GetFrequency() const;
|
||||
|
||||
KYTY_CLASS_NO_COPY(Timer);
|
||||
|
||||
[[nodiscard]] static uint64_t QueryPerformanceFrequency();
|
||||
[[nodiscard]] static uint64_t QueryPerformanceCounter();
|
||||
|
||||
private:
|
||||
bool m_is_paused = true;
|
||||
uint64_t m_Frequency = 0;
|
||||
uint64_t m_StartTime = 0;
|
||||
uint64_t m_PauseTime = 0;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_TIMER_H_ */
|
||||
@@ -0,0 +1,82 @@
|
||||
#include "common/virtualMemory.h"
|
||||
|
||||
#include "common/platform/sysVirtual.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
namespace VirtualMemory {
|
||||
|
||||
void Init() {
|
||||
SysVirtualInit();
|
||||
}
|
||||
|
||||
uint64_t Alloc(uint64_t address, uint64_t size, Mode mode) {
|
||||
return SysVirtualAlloc(address, size, mode);
|
||||
}
|
||||
|
||||
uint64_t AllocAligned(uint64_t address, uint64_t size, Mode mode, uint64_t alignment) {
|
||||
return SysVirtualAllocAligned(address, size, mode, alignment);
|
||||
}
|
||||
|
||||
bool AllocFixed(uint64_t address, uint64_t size, Mode mode) {
|
||||
return SysVirtualAllocFixed(address, size, mode);
|
||||
}
|
||||
|
||||
bool Commit(uint64_t address, uint64_t size, Mode mode) {
|
||||
return SysVirtualCommit(address, size, mode);
|
||||
}
|
||||
|
||||
uint64_t Reserve(uint64_t address, uint64_t size) {
|
||||
return SysVirtualReserve(address, size);
|
||||
}
|
||||
|
||||
uint64_t ReserveAligned(uint64_t address, uint64_t size, uint64_t alignment) {
|
||||
return SysVirtualReserveAligned(address, size, alignment);
|
||||
}
|
||||
|
||||
bool ReserveFixed(uint64_t address, uint64_t size) {
|
||||
return SysVirtualReserveFixed(address, size);
|
||||
}
|
||||
|
||||
bool Decommit(uint64_t address, uint64_t size) {
|
||||
return SysVirtualDecommit(address, size);
|
||||
}
|
||||
|
||||
bool Free(uint64_t address) {
|
||||
return SysVirtualFree(address);
|
||||
}
|
||||
|
||||
bool FreeRange(uint64_t address, uint64_t size) {
|
||||
return SysVirtualFreeRange(address, size);
|
||||
}
|
||||
|
||||
bool Protect(uint64_t address, uint64_t size, Mode mode, Mode* old_mode) {
|
||||
return SysVirtualProtect(address, size, mode, old_mode);
|
||||
}
|
||||
|
||||
bool FlushInstructionCache(uint64_t address, uint64_t size) {
|
||||
return SysVirtualFlushInstructionCache(address, size);
|
||||
}
|
||||
|
||||
bool PatchReplace(uint64_t vaddr, uint64_t value) {
|
||||
Mode old_mode {};
|
||||
Protect(vaddr, 8, Mode::ReadWrite, &old_mode);
|
||||
|
||||
auto* ptr = reinterpret_cast<uint64_t*>(vaddr);
|
||||
|
||||
bool ret = (*ptr != value);
|
||||
|
||||
*ptr = value;
|
||||
|
||||
Protect(vaddr, 8, old_mode);
|
||||
|
||||
if (IsExecute(old_mode)) {
|
||||
FlushInstructionCache(vaddr, 8);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace VirtualMemory
|
||||
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,46 @@
|
||||
#ifndef KYTY_COMMON_VIRTUALMEMORY_H_
|
||||
#define KYTY_COMMON_VIRTUALMEMORY_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
namespace VirtualMemory {
|
||||
|
||||
enum class Mode : uint32_t {
|
||||
NoAccess = 0,
|
||||
Read = 1,
|
||||
Write = 2,
|
||||
ReadWrite = Read | Write,
|
||||
Execute = 4,
|
||||
ExecuteRead = Execute | Read,
|
||||
ExecuteWrite = Execute | Write,
|
||||
ExecuteReadWrite = Execute | Read | Write,
|
||||
};
|
||||
|
||||
inline bool IsExecute(Mode mode) {
|
||||
return (mode == Mode::Execute || mode == Mode::ExecuteRead || mode == Mode::ExecuteWrite ||
|
||||
mode == Mode::ExecuteReadWrite);
|
||||
}
|
||||
|
||||
void Init();
|
||||
|
||||
uint64_t Alloc(uint64_t address, uint64_t size, Mode mode);
|
||||
uint64_t AllocAligned(uint64_t address, uint64_t size, Mode mode, uint64_t alignment);
|
||||
bool AllocFixed(uint64_t address, uint64_t size, Mode mode);
|
||||
bool Commit(uint64_t address, uint64_t size, Mode mode);
|
||||
uint64_t Reserve(uint64_t address, uint64_t size);
|
||||
uint64_t ReserveAligned(uint64_t address, uint64_t size, uint64_t alignment);
|
||||
bool ReserveFixed(uint64_t address, uint64_t size);
|
||||
bool Decommit(uint64_t address, uint64_t size);
|
||||
bool Free(uint64_t address);
|
||||
bool FreeRange(uint64_t address, uint64_t size);
|
||||
bool Protect(uint64_t address, uint64_t size, Mode mode, Mode* old_mode = nullptr);
|
||||
bool FlushInstructionCache(uint64_t address, uint64_t size);
|
||||
bool PatchReplace(uint64_t vaddr, uint64_t value);
|
||||
|
||||
} // namespace VirtualMemory
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_VIRTUALMEMORY_H_ */
|
||||
Reference in new issue
Block a user