Files
Horizon-OC/Source/sys-clk-OC/sysmodule/src/file_utils.cpp

362 lines
9.3 KiB
C++

/*
* --------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net>
* wrote this file. As long as you retain this notice you can do whatever you
* want with this stuff. If you meet any of us some day, and you think this
* stuff is worth it, you can buy us a beer in return. - The sys-clk authors
* --------------------------------------------------------------------------
*/
#include "file_utils.h"
#include "clocks.h"
#include <dirent.h>
#include <nxExt.h>
static LockableMutex g_log_mutex;
static LockableMutex g_csv_mutex;
static std::atomic_bool g_has_initialized = false;
static bool g_log_enabled = false;
static std::uint64_t g_last_flag_check = 0;
extern "C" void __libnx_init_time(void);
static void _FileUtils_InitializeThreadFunc(void *args)
{
FileUtils::Initialize();
}
bool FileUtils::IsInitialized()
{
return g_has_initialized;
}
void FileUtils::LogLine(const char *format, ...)
{
va_list args;
va_start(args, format);
if (g_has_initialized)
{
g_log_mutex.Lock();
FileUtils::RefreshFlags(false);
if(g_log_enabled)
{
FILE *file = fopen(FILE_LOG_FILE_PATH, "a");
if (file)
{
struct timespec now;
clock_gettime(CLOCK_REALTIME, &now);
struct tm* nowTm = localtime(&now.tv_sec);
fprintf(file, "[%04d-%02d-%02d %02d:%02d:%02d.%03ld] ", nowTm->tm_year+1900, nowTm->tm_mon+1, nowTm->tm_mday, nowTm->tm_hour, nowTm->tm_min, nowTm->tm_sec, now.tv_nsec / 1000000UL);
vfprintf(file, format, args);
fprintf(file, "\n");
fclose(file);
}
}
g_log_mutex.Unlock();
}
va_end(args);
}
void FileUtils::WriteContextToCsv(const SysClkContext* context)
{
std::scoped_lock lock{g_csv_mutex};
FILE *file = fopen(FILE_CONTEXT_CSV_PATH, "a");
if (file)
{
// Print header
if(!ftell(file))
{
fprintf(file, "timestamp,profile,app_tid");
for (unsigned int module = 0; module < SysClkModule_EnumMax; module++)
{
fprintf(file, ",%s_hz", sysclkFormatModule((SysClkModule)module, false));
}
for (unsigned int sensor = 0; sensor < SysClkThermalSensor_EnumMax; sensor++)
{
fprintf(file, ",%s_milliC", sysclkFormatThermalSensor((SysClkThermalSensor)sensor, false));
}
fprintf(file, "\n");
}
struct timespec now;
clock_gettime(CLOCK_REALTIME, &now);
fprintf(file, "%ld%03ld,%s,%016lx", now.tv_sec, now.tv_nsec / 1000000UL, sysclkFormatProfile(context->profile, false), context->applicationId);
for (unsigned int module = 0; module < SysClkModule_EnumMax; module++)
{
fprintf(file, ",%d", context->freqs[module]);
}
for (unsigned int sensor = 0; sensor < SysClkThermalSensor_EnumMax; sensor++)
{
fprintf(file, ",%d", context->temps[sensor]);
}
fprintf(file, "\n");
fclose(file);
}
}
void FileUtils::RefreshFlags(bool force)
{
std::uint64_t now = armTicksToNs(armGetSystemTick());
if(!force && (now - g_last_flag_check) < FILE_FLAG_CHECK_INTERVAL_NS)
{
return;
}
FILE *file = fopen(FILE_LOG_FLAG_PATH, "r");
if (file)
{
g_log_enabled = true;
fclose(file);
} else {
g_log_enabled = false;
}
g_last_flag_check = now;
}
void FileUtils::InitializeAsync()
{
Thread initThread = {0};
threadCreate(&initThread, _FileUtils_InitializeThreadFunc, NULL, NULL, 0x2000, 0x15, 0);
threadStart(&initThread);
}
Result FileUtils::Initialize()
{
Result rc = 0;
if (R_SUCCEEDED(rc))
{
rc = timeInitialize();
}
__libnx_init_time();
timeExit();
if (R_SUCCEEDED(rc))
{
rc = fsInitialize();
}
if (R_SUCCEEDED(rc))
{
rc = fsdevMountSdmc();
}
if (R_SUCCEEDED(rc))
{
FileUtils::RefreshFlags(true);
g_has_initialized = true;
FileUtils::LogLine("=== " TARGET " " TARGET_VERSION " ===");
}
return rc;
}
void FileUtils::Exit()
{
if (!g_has_initialized)
{
return;
}
g_has_initialized = false;
g_log_enabled = false;
fsdevUnmountAll();
fsExit();
}
void FileUtils::ParseLoaderKip() {
const char* dirs[] = { "/", "/atmosphere/", "/atmosphere/kips/", "/bootloader/" };
char* full_path = new char[0x200];
for (auto const& dir : dirs) {
struct dirent *entry = NULL;
DIR *dp = opendir(dir);
if (!dp)
continue;
while ((entry = readdir(dp))) {
if (entry->d_type != DT_REG)
continue;
snprintf(full_path, 0x200, "%s%s", dir, entry->d_name);
FILE* fp = fopen(full_path, "r");
if (!fp)
continue;
fseek(fp, 0, SEEK_END);
long res = ftell(fp);
fclose(fp);
if (res == -1)
continue;
size_t filesize = (size_t)res;
if (filesize < 0x1000 || filesize > 512 * 1024)
continue;
const char kip_ext[] = {'.', 'k', 'i', 'p'};
size_t file_name_len = strnlen(reinterpret_cast<const char*>(&entry->d_name), 256);
const char* file_ext = &entry->d_name[file_name_len - sizeof(kip_ext)];
if (strncasecmp((const char*)kip_ext, file_ext, sizeof(kip_ext)))
continue;
if (R_SUCCEEDED(CustParser(full_path, filesize))) {
LogLine("Parsed cust config from %s, maxMemFreq: %u MHz, boostCpuFreq: %u MHz",
full_path,
Clocks::maxMemFreq / 1'000'000,
Clocks::boostCpuFreq / 1'000'000
);
delete[] full_path;
return;
}
}
closedir(dp);
}
delete[] full_path;
}
Result FileUtils::CustParser(const char* filepath, size_t filesize) {
enum ParseError {
ParseError_Success = 0,
ParseError_OpenReadFailed,
ParseError_WrongKipMagic,
ParseError_CustNotFound,
ParseError_WrongCustRev,
};
FILE* fp = fopen(filepath, "r");
if (!fp)
return ParseError_OpenReadFailed;
constexpr uint8_t KIP_MAGIC[] = {'K', 'I', 'P', '1', 'L', 'o', 'a', 'd', 'e', 'r'};
constexpr size_t BLOCK_SIZE = 0x1000;
char* tmp_block = new char[BLOCK_SIZE];
fread(tmp_block, sizeof(char), BLOCK_SIZE, fp);
if (memcmp(KIP_MAGIC, tmp_block, sizeof(KIP_MAGIC))) {
delete[] tmp_block;
return ParseError_WrongKipMagic;
}
CustTable table {};
fpos_t cust_pos = 0;
long block_pos = 0;
while ((block_pos = ftell(fp)) >= 0 && (size_t)block_pos < filesize) {
for (size_t i = 0; i < BLOCK_SIZE; i += sizeof(table.cust)) {
if (memcmp(table.cust, &tmp_block[i], sizeof(table.cust)))
continue;
fgetpos(fp, &cust_pos);
cust_pos = cust_pos + i - BLOCK_SIZE;
goto found;
}
fread(tmp_block, sizeof(char), BLOCK_SIZE, fp);
}
found:
delete[] tmp_block;
if (!cust_pos) {
fclose(fp);
return ParseError_CustNotFound;
}
memset(reinterpret_cast<void*>(&table), 0, sizeof(CustTable));
fsetpos(fp, &cust_pos);
if (!fread(reinterpret_cast<char*>(&table), 1, sizeof(CustTable), fp)) {
fclose(fp);
return ParseError_OpenReadFailed;
}
fclose(fp);
if (table.custRev != 2)
return ParseError_WrongCustRev;
if (Clocks::GetIsMariko()) {
if (table.marikoCpuBoostClock)
Clocks::boostCpuFreq = table.marikoCpuBoostClock * 1000;
if (table.marikoEmcMaxClock)
Clocks::maxMemFreq = table.marikoEmcMaxClock * 1000;
} else {
if (table.eristaEmcMaxClock)
Clocks::maxMemFreq = table.eristaEmcMaxClock * 1000;
}
return ParseError_Success;
}
Result FileUtils::mkdir_p(const char* dirpath) {
// https://gist.github.com/JonathonReinhart/8c0d90191c38af2dcadb102c4e202950
auto mkdir_wrapper = [](char* path) {
errno = 0;
size_t len = strnlen(path, 0x1000);
bool isCWDir = (len == 0) || (len == 1 && (path[0] == '.' || path[0] == '/'));
if (isCWDir)
return 0;
if (R_SUCCEEDED(mkdir(path, S_IRWXU)))
return 0;
struct stat st;
if (errno == EEXIST &&
R_SUCCEEDED(stat(path, &st)) &&
S_ISDIR(st.st_mode)) {
errno = 0;
return 0;
}
errno = ENOTDIR;
return -1;
};
errno = 0;
Result res = 0;
size_t path_len = strnlen(dirpath, 0x1000);
char* path_copy = new char[path_len];
memcpy(path_copy, dirpath, path_len);
char* p = path_copy;
while (*p) {
if (*p == '/') {
// Temporarily truncate
*p = '\0';
if (R_FAILED(mkdir_wrapper(path_copy))) {
res = -1;
goto end;
}
*p = '/';
}
p++;
}
if (R_FAILED(mkdir_wrapper(path_copy)))
res = -1;
end:
delete[] path_copy;
return res;
}