rewrite everything

This commit is contained in:
souldbminersmwc
2025-09-17 19:56:06 -04:00
parent a1bfcebba8
commit f3eae72b47
177 changed files with 49152 additions and 1258 deletions

View File

@@ -0,0 +1,60 @@
/********************************************************************************
* File: debug_funcs.cpp
* Author: ppkantorski
* Description:
* This source file contains the implementation of debugging functions for the
* Ultrahand Overlay project.
********************************************************************************/
#include "debug_funcs.hpp"
namespace ult {
#if USING_LOGGING_DIRECTIVE
// Define static variables
const std::string defaultLogFilePath = "sdmc:/switch/.packages/log.txt";
std::string logFilePath = defaultLogFilePath;
bool disableLogging = true;
std::mutex logMutex;
void logMessage(const std::string& message) {
//if (disableLogging)
// return;
std::time_t currentTime = std::time(nullptr);
std::tm* timeInfo = std::localtime(&currentTime);
char timestamp[30]; // Exact size for "[YYYY-MM-DD HH:MM:SS] " + null terminator
strftime(timestamp, sizeof(timestamp), "[%Y-%m-%d %H:%M:%S] ", timeInfo);
// Depending on the directive, use either std::ofstream or stdio functions
#if !USING_FSTREAM_DIRECTIVE
// Use stdio functions to open, write, and close the file
{
std::lock_guard<std::mutex> lock(logMutex); // Locks the mutex for thread-safe access
FILE* file = fopen(logFilePath.c_str(), "a"); // Open the file in append mode
if (file != nullptr) {
fprintf(file, "%s%s\n", timestamp, message.c_str());
fclose(file); // Close the file after writing
} else {
// Handle error when file opening fails (if necessary)
// printf("Failed to open log file: %s\n", logFilePath.c_str());
}
}
#else
// Use std::ofstream if !USING_FSTREAM_DIRECTIVE is not defined
{
std::lock_guard<std::mutex> lock(logMutex); // Locks the mutex for the duration of this block
std::ofstream file(logFilePath.c_str(), std::ios::app);
if (file.is_open()) {
file << timestamp << message << "\n";
} else {
// Handle error when file opening fails
// std::cerr << "Failed to open log file: " << logFilePath << std::endl;
}
}
#endif
}
#endif
}

View File

@@ -0,0 +1,812 @@
/********************************************************************************
* File: download_funcs.cpp
* Author: ppkantorski
* Description:
* This source file provides implementations for the functions declared in
* download_funcs.hpp. These functions utilize libcurl for downloading files
* from the internet and minizip-ng for extracting ZIP archives with proper
* 64-bit file support.
*
* For the latest updates and contributions, visit the project's GitHub repository.
* (GitHub Repository: https://github.com/ppkantorski/Ultrahand-Overlay)
*
* Note: Please be aware that this notice cannot be altered or removed. It is a part
* of the project's documentation and must remain intact.
*
* Licensed under both GPLv2 and CC-BY-4.0
* Copyright (c) 2024 ppkantorski
********************************************************************************/
#include "download_funcs.hpp"
namespace ult {
size_t DOWNLOAD_READ_BUFFER = 16*1024;//64 * 1024;//4096*10;
size_t DOWNLOAD_WRITE_BUFFER = 16*1024;//64 * 1024;
size_t UNZIP_READ_BUFFER = 32*1024;//131072*2;//4096*4;
size_t UNZIP_WRITE_BUFFER = 16*1024;//131072*2;//4096*4;
// Path to the CA certificate
//const std::string cacertPath = "sdmc:/config/ultrahand/cacert.pem";
//const std::string cacertURL = "https://curl.se/ca/cacert.pem";
// Shared atomic flag to indicate whether to abort the download operation
std::atomic<bool> abortDownload(false);
// Define an atomic bool for interpreter completion
std::atomic<bool> abortUnzip(false);
std::atomic<int> downloadPercentage(-1);
std::atomic<int> unzipPercentage(-1);
// Thread-safe curl initialization
static std::mutex curlInitMutex;
static std::atomic<bool> curlInitialized(false);
struct FileDeleter {
void operator()(FILE* f) const { if (f) fclose(f); }
};
// Definition of CurlDeleter
void CurlDeleter::operator()(CURL* curl) const {
if (curl) {
curl_easy_cleanup(curl);
}
}
// Callback function to write received data to a file.
#if !USING_FSTREAM_DIRECTIVE
// Using stdio.h functions (FILE*, fwrite)
size_t writeCallback(void* ptr, size_t size, size_t nmemb, FILE* stream) {
if (!ptr || !stream) return 0;
//size_t totalBytes = size * nmemb;
//size_t writtenBytes = fwrite(ptr, 1, totalBytes, stream);
//return writtenBytes;
return fwrite(ptr, 1, size * nmemb, stream);
}
#else
// Using std::ofstream for writing
size_t writeCallback(void* ptr, size_t size, size_t nmemb, std::ostream* stream) {
if (!ptr || !stream) return 0;
auto& file = *static_cast<std::ofstream*>(stream);
//size_t totalBytes = size * nmemb;
file.write(static_cast<const char*>(ptr), size * nmemb);
return totalBytes;
}
#endif
// Your C function
int progressCallback(void *ptr, curl_off_t totalToDownload, curl_off_t nowDownloaded, curl_off_t totalToUpload, curl_off_t nowUploaded) {
if (!ptr) return 1;
auto percentage = static_cast<std::atomic<int>*>(ptr);
if (totalToDownload > 0) {
//int newProgress = static_cast<int>((static_cast<double>(nowDownloaded) / static_cast<double>(totalToDownload)) * 100.0);
percentage->store(static_cast<int>((static_cast<double>(nowDownloaded) / static_cast<double>(totalToDownload)) * 100.0), std::memory_order_release);
}
if (abortDownload.load(std::memory_order_acquire)) {
percentage->store(-1, std::memory_order_release);
return 1; // Abort the download
}
return 0; // Continue the download
}
// Global initialization function
void initializeCurl() {
std::lock_guard<std::mutex> lock(curlInitMutex);
if (!curlInitialized.load(std::memory_order_acquire)) {
const CURLcode res = curl_global_init(CURL_GLOBAL_DEFAULT);
if (res != CURLE_OK) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("curl_global_init() failed: " + std::string(curl_easy_strerror(res)));
#endif
// Handle error appropriately, possibly exit the program
} else {
curlInitialized.store(true, std::memory_order_release);
}
}
}
// Global cleanup function
void cleanupCurl() {
std::lock_guard<std::mutex> lock(curlInitMutex);
if (curlInitialized.load(std::memory_order_acquire)) {
curl_global_cleanup();
curlInitialized.store(false, std::memory_order_release);
}
}
/**
* @brief Downloads a file from a URL to a specified destination.
*
* @param url The URL of the file to download.
* @param toDestination The destination path where the file should be saved.
* @return True if the download was successful, false otherwise.
*/
bool downloadFile(const std::string& url, const std::string& toDestination, bool noPercentagePolling) {
abortDownload.store(false, std::memory_order_release);
if (url.find_first_of("{}") != std::string::npos) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Invalid URL: " + url);
#endif
return false;
}
std::string destination = toDestination;
if (destination.back() == '/') {
createDirectory(destination);
const size_t lastSlash = url.find_last_of('/');
if (lastSlash != std::string::npos) {
destination += url.substr(lastSlash + 1);
} else {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Invalid URL: " + url);
#endif
return false;
}
} else {
createDirectory(destination.substr(0, destination.find_last_of('/')));
}
const std::string tempFilePath = getParentDirFromPath(destination) + "." + getFileName(destination) + ".tmp";
#if USING_FSTREAM_DIRECTIVE
// Use ofstream if !USING_FSTREAM_DIRECTIVE is not defined
std::ofstream file(tempFilePath, std::ios::binary);
if (!file.is_open()) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Error opening file: " + tempFilePath);
#endif
return false;
}
#else
// Alternative method of opening file (depending on your platform, like using POSIX open())
std::unique_ptr<FILE, FileDeleter> file(fopen(tempFilePath.c_str(), "wb"));
if (!file) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Error opening file: " + tempFilePath);
#endif
return false;
}
// ADD THIS: Set up write buffer for better performance
std::unique_ptr<char[]> writeBuffer;
if (DOWNLOAD_WRITE_BUFFER > 0) {
writeBuffer = std::make_unique<char[]>(DOWNLOAD_WRITE_BUFFER);
// _IOFBF = full buffering, _IOLBF = line buffering, _IONBF = no buffering
setvbuf(file.get(), writeBuffer.get(), _IOFBF, DOWNLOAD_WRITE_BUFFER);
}
#endif
// Ensure curl is initialized
initializeCurl();
std::unique_ptr<CURL, CurlDeleter> curl(curl_easy_init());
if (!curl) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Error initializing curl.");
#endif
#if USING_FSTREAM_DIRECTIVE
file.close();
#else
file.reset();
#endif
return false;
}
// Only initialize downloadPercentage if we're tracking progress
if (!noPercentagePolling) {
downloadPercentage.store(0, std::memory_order_release);
}
curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str());
curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, writeCallback);
#if USING_FSTREAM_DIRECTIVE
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &file);
#else
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, file.get());
#endif
// Conditionally set up progress callback based on noPercentagePolling
if (noPercentagePolling) {
// Disable progress function entirely
curl_easy_setopt(curl.get(), CURLOPT_NOPROGRESS, 1L);
} else {
// Enable progress callback for percentage updates
curl_easy_setopt(curl.get(), CURLOPT_NOPROGRESS, 0L);
curl_easy_setopt(curl.get(), CURLOPT_XFERINFOFUNCTION, progressCallback);
curl_easy_setopt(curl.get(), CURLOPT_XFERINFODATA, &downloadPercentage);
}
curl_easy_setopt(curl.get(), CURLOPT_USERAGENT, userAgent);
curl_easy_setopt(curl.get(), CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS); // Enable HTTP/2
curl_easy_setopt(curl.get(), CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); // Force TLS 1.2
curl_easy_setopt(curl.get(), CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl.get(), CURLOPT_BUFFERSIZE, DOWNLOAD_READ_BUFFER); // Increase buffer size
// Add timeout options
curl_easy_setopt(curl.get(), CURLOPT_CONNECTTIMEOUT, 10L); // 10 seconds to connect
curl_easy_setopt(curl.get(), CURLOPT_LOW_SPEED_LIMIT, 1L); // 1 byte/s (virtually any progress)
curl_easy_setopt(curl.get(), CURLOPT_LOW_SPEED_TIME, 60L); // 1 minutes of no progress
CURLcode result = curl_easy_perform(curl.get());
#if USING_FSTREAM_DIRECTIVE
file.close();
#else
file.reset();
#endif
if (result != CURLE_OK) {
#if USING_LOGGING_DIRECTIVE
if (result == CURLE_OPERATION_TIMEDOUT) {
if (!disableLogging)
logMessage("Download timed out: " + url);
} else if (result == CURLE_COULDNT_CONNECT) {
if (!disableLogging)
logMessage("Could not connect to: " + url);
} else {
if (!disableLogging)
logMessage("Error downloading file: " + std::string(curl_easy_strerror(result)));
}
#endif
deleteFileOrDirectory(tempFilePath);
// Only update percentage if we're tracking it
if (!noPercentagePolling) {
downloadPercentage.store(-1, std::memory_order_release);
}
return false;
}
#if USING_FSTREAM_DIRECTIVE
std::ifstream checkFile(tempFilePath);
if (!checkFile || checkFile.peek() == std::ifstream::traits_type::eof()) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Error downloading file: Empty file");
#endif
deleteFileOrDirectory(tempFilePath);
// Only update percentage if we're tracking it
if (!noPercentagePolling) {
downloadPercentage.store(-1, std::memory_order_release);
}
checkFile.close();
return false;
}
checkFile.close();
#else
// Alternative method for checking if the file is empty (POSIX example)
struct stat fileStat;
if (stat(tempFilePath.c_str(), &fileStat) != 0 || fileStat.st_size == 0) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Error downloading file: Empty file");
#endif
deleteFileOrDirectory(tempFilePath);
// Only update percentage if we're tracking it
if (!noPercentagePolling) {
downloadPercentage.store(-1, std::memory_order_release);
}
return false;
}
#endif
// Only update percentage if we're tracking it
if (!noPercentagePolling) {
downloadPercentage.store(100, std::memory_order_release);
}
// CHECK FOR PROTECTED FILES AND ADD .ultra EXTENSION IF NEEDED
if (PROTECTED_FILES.find(destination) != PROTECTED_FILES.end()) {
destination += ".ultra";
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Protected file detected, renaming download to: " + destination);
#endif
}
moveFile(tempFilePath, destination);
return true;
}
/**
* @brief Custom I/O function for opening files with larger buffer
*/
static voidpf ZCALLBACK fopen64_file_func_custom(voidpf opaque, const void* filename, int mode) {
FILE* file = nullptr;
const char* mode_fopen = nullptr;
if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ)
mode_fopen = "rb";
else if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
mode_fopen = "r+b";
else if (mode & ZLIB_FILEFUNC_MODE_CREATE)
mode_fopen = "wb";
if ((filename != nullptr) && (mode_fopen != nullptr)) {
file = fopen((const char*)filename, mode_fopen);
if (file && ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ)) {
// Set 64KB buffer for reading the ZIP file - reduces syscalls
//static const size_t zipReadBufferSize = UNZIP_READ_BUFFER;
setvbuf(file, nullptr, _IOFBF, UNZIP_READ_BUFFER);
}
}
return file;
}
static int ZCALLBACK fclose64_file_func_custom(voidpf opaque, voidpf stream) {
int ret = EOF;
if (stream != nullptr) {
ret = fclose((FILE*)stream);
}
return ret;
}
/**
* @brief Extracts files from a ZIP archive to a specified destination.
*
* Ultra-optimized single-pass extraction with smooth byte-based progress reporting
* using miniz with proper 64-bit file support and streaming extraction.
* Fixed memory leaks with RAII for proper resource cleanup on abort.
*
* @param zipFilePath The path to the ZIP archive file.
* @param toDestination The destination directory where files should be extracted.
* @return True if the extraction was successful, false otherwise.
*/
bool unzipFile(const std::string& zipFilePath, const std::string& toDestination) {
abortUnzip.store(false, std::memory_order_release);
unzipPercentage.store(0, std::memory_order_release);
// Time-based abort checking - pre-calculated constants
//u64 lastAbortCheck = armTicksToNs(armGetSystemTick());
//u64 currentNanos; // Reused for all tick operations
bool success = true;
// RAII wrapper for unzFile
struct UnzFileManager {
unzFile file = nullptr;
UnzFileManager(const std::string& path) {
zlib_filefunc64_def ffunc;
fill_fopen64_filefunc(&ffunc);
ffunc.zopen64_file = fopen64_file_func_custom;
ffunc.zclose_file = fclose64_file_func_custom;
file = unzOpen2_64(path.c_str(), &ffunc);
}
~UnzFileManager() {
if (file) {
unzClose(file);
file = nullptr;
}
}
bool is_valid() const { return file != nullptr; }
operator unzFile() const { return file; }
};
// RAII wrapper for output file
struct OutputFileManager {
#if !USING_FSTREAM_DIRECTIVE
FILE* file = nullptr;
std::unique_ptr<char[]> buffer;
size_t bufferSize;
OutputFileManager(size_t bufSize) : bufferSize(bufSize) {
buffer = std::make_unique<char[]>(bufferSize);
}
bool open(const std::string& path) {
close();
file = fopen(path.c_str(), "wb");
if (file) {
setvbuf(file, buffer.get(), _IOFBF, bufferSize);
}
return file != nullptr;
}
void close() {
if (file) {
fclose(file);
file = nullptr;
}
}
bool is_open() const { return file != nullptr; }
size_t write(const void* data, size_t size) {
return file ? fwrite(data, 1, size, file) : 0;
}
~OutputFileManager() { close(); }
#else
std::ofstream file;
OutputFileManager(size_t bufSize) {
// Constructor for consistency with FILE* version
}
bool open(const std::string& path) {
close();
file.open(path, std::ios::binary);
if (file.is_open()) {
file.rdbuf()->pubsetbuf(nullptr, UNZIP_WRITE_BUFFER);
}
return file.is_open();
}
void close() {
if (file.is_open()) {
file.close();
}
}
bool is_open() const { return file.is_open(); }
size_t write(const void* data, size_t size) {
if (file.is_open()) {
file.write(static_cast<const char*>(data), size);
return file.good() ? size : 0;
}
return 0;
}
#endif
};
UnzFileManager zipFile(zipFilePath);
if (!zipFile.is_valid()) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Failed to open zip file: " + zipFilePath);
#endif
return false;
}
// Get global info about the ZIP file
unz_global_info64 globalInfo;
if (unzGetGlobalInfo64(zipFile, &globalInfo) != UNZ_OK) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Failed to get zip file info");
#endif
return false;
}
const uLong numFiles = globalInfo.number_entry;
if (numFiles == 0) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("No files found in archive");
#endif
return false;
}
// ALWAYS calculate total size for accurate byte-based progress
ZPOS64_T totalUncompressedSize = 0;
char tempFilenameBuffer[512];
unz_file_info64 fileInfo;
// First pass: calculate total uncompressed size
int result = unzGoToFirstFile(zipFile);
while (result == UNZ_OK) {
// Time-based abort check at start of each file (only if 2+ seconds have passed)
//currentNanos = armTicksToNs(armGetSystemTick());
//if ((currentNanos - lastAbortCheck) >= 2000000000ULL) {
// if (abortUnzip.load(std::memory_order_relaxed)) {
// unzipPercentage.store(-1, std::memory_order_release);
// #if USING_LOGGING_DIRECTIVE
// logMessage("Extraction aborted during size calculation");
// #endif
// abortUnzip.store(false, std::memory_order_release);
// return false;
// }
// lastAbortCheck = currentNanos;
//}
if (abortUnzip.load(std::memory_order_relaxed)) {
unzipPercentage.store(-1, std::memory_order_release);
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Extraction aborted during size calculation");
#endif
abortUnzip.store(false, std::memory_order_release);
return false;
}
if (unzGetCurrentFileInfo64(zipFile, &fileInfo, tempFilenameBuffer, sizeof(tempFilenameBuffer),
nullptr, 0, nullptr, 0) == UNZ_OK) {
const size_t nameLen = strlen(tempFilenameBuffer);
if (nameLen > 0 && tempFilenameBuffer[nameLen - 1] != '/') {
totalUncompressedSize += std::max(fileInfo.uncompressed_size, static_cast<ZPOS64_T>(1));
}
}
result = unzGoToNextFile(zipFile);
}
// Fallback to 1 if no actual data (avoid division by zero)
if (totalUncompressedSize == 0) {
totalUncompressedSize = 1;
}
#if USING_LOGGING_DIRECTIVE
if (!disableLogging) {
logMessage("Processing " + std::to_string(numFiles) + " files, " +
std::to_string(totalUncompressedSize) + " total bytes from archive");
}
#endif
// Pre-allocate ALL reusable strings and variables outside the main loop
std::string fileName, extractedFilePath, directoryPath;
//fileName.reserve(512);
//extractedFilePath.reserve(1024);
//directoryPath.reserve(1024);
// Single large buffer for extraction - reused for all files
const size_t bufferSize = UNZIP_WRITE_BUFFER;
std::unique_ptr<char[]> buffer = std::make_unique<char[]>(bufferSize);
char filenameBuffer[512]; // Stack allocated for filename reading
// Progress tracking variables - OPTIMIZED for smooth byte-based tracking
ZPOS64_T totalBytesProcessed = 0;
uLong filesProcessed = 0;
int currentProgress = 0; // Current percentage (0-100)
// Create output file manager
OutputFileManager outputFile(bufferSize);
// Loop variables moved outside
bool extractSuccess;
ZPOS64_T fileBytesProcessed;
int bytesRead;
// String operation variables
const char* filename;
size_t nameLen;
size_t lastSlashPos;
size_t invalid_pos;
size_t start_pos;
// Ensure destination directory exists
createDirectory(toDestination);
// Ensure destination ends with '/' - pre-allocate final string
std::string destination;
//destination.reserve(toDestination.size() + 1);
destination = toDestination;
if (!destination.empty() && destination.back() != '/') {
destination += '/';
}
int newProgress;;
// Extract files
result = unzGoToFirstFile(zipFile);
while (result == UNZ_OK && success) {
// Time-based abort check at start of each file (only if 2+ seconds have passed)
//currentNanos = armTicksToNs(armGetSystemTick());
//if ((currentNanos - lastAbortCheck) >= 2000000000ULL) {
// if (abortUnzip.load(std::memory_order_relaxed)) {
// success = false;
// break; // RAII will handle cleanup
// }
// lastAbortCheck = currentNanos;
//}
if (abortUnzip.load(std::memory_order_relaxed)) {
success = false;
break; // RAII will handle cleanup
}
// Get current file info - reuse fileInfo variable
if (unzGetCurrentFileInfo64(zipFile, &fileInfo, filenameBuffer, sizeof(filenameBuffer),
nullptr, 0, nullptr, 0) != UNZ_OK) {
result = unzGoToNextFile(zipFile);
continue;
}
filename = filenameBuffer;
// Quick filename validation
if (!filename || filename[0] == '\0') {
result = unzGoToNextFile(zipFile);
continue;
}
nameLen = strlen(filename);
if (nameLen > 0 && filename[nameLen - 1] == '/') { // Skip directories
result = unzGoToNextFile(zipFile);
continue;
}
// Build extraction path - reuse allocated strings
fileName.assign(filename, nameLen);
//extractedFilePath.clear();
extractedFilePath = destination;
extractedFilePath += fileName;
// Optimized character cleaning - only if needed
invalid_pos = extractedFilePath.find_first_of(":*?\"<>|");
if (invalid_pos != std::string::npos) {
start_pos = std::min(extractedFilePath.find(ROOT_PATH) + 5, extractedFilePath.size());
auto it = extractedFilePath.begin() + start_pos;
extractedFilePath.erase(std::remove_if(it, extractedFilePath.end(), [](char c) {
return c == ':' || c == '*' || c == '?' || c == '\"' || c == '<' || c == '>' || c == '|';
}), extractedFilePath.end());
}
// CHECK FOR PROTECTED FILES AND ADD .ultra EXTENSION IF NEEDED
if (PROTECTED_FILES.find(extractedFilePath) != PROTECTED_FILES.end()) {
extractedFilePath += ".ultra";
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Protected file detected, renaming to: " + extractedFilePath);
#endif
}
// Open the current file in the ZIP
if (unzOpenCurrentFile(zipFile) != UNZ_OK) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Could not open file in ZIP: " + fileName);
#endif
result = unzGoToNextFile(zipFile);
continue;
}
// Create directory if needed
lastSlashPos = extractedFilePath.find_last_of('/');
if (lastSlashPos != std::string::npos) {
directoryPath.assign(extractedFilePath, 0, lastSlashPos + 1);
createDirectory(directoryPath);
}
// Open output file
if (!outputFile.open(extractedFilePath)) {
unzCloseCurrentFile(zipFile);
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Error creating file: " + extractedFilePath);
#endif
result = unzGoToNextFile(zipFile);
continue;
}
// Extract file data in chunks
extractSuccess = true;
fileBytesProcessed = 0;
while ((bytesRead = unzReadCurrentFile(zipFile, buffer.get(), bufferSize)) > 0) {
if (abortUnzip.load(std::memory_order_relaxed)) {
extractSuccess = false;
break; // RAII will handle cleanup
}
// Write data to file
if (outputFile.write(buffer.get(), bytesRead) != static_cast<size_t>(bytesRead)) {
extractSuccess = false;
break;
}
// Update progress tracking
fileBytesProcessed += bytesRead;
totalBytesProcessed += bytesRead;
// FIXED: Allow progress to reach 100% naturally during processing
if (totalUncompressedSize > 0) {
newProgress = static_cast<int>((totalBytesProcessed * 100) / totalUncompressedSize);
if (newProgress > currentProgress && newProgress <= 100) {
currentProgress = newProgress;
unzipPercentage.store(currentProgress, std::memory_order_release);
#if USING_LOGGING_DIRECTIVE
// Only log at 10% intervals to avoid spam
if (currentProgress % 10 == 0) {
if (!disableLogging) {
logMessage("Progress: " + std::to_string(currentProgress) + "% (" +
std::to_string(totalBytesProcessed) + "/" +
std::to_string(totalUncompressedSize) + " bytes)");
}
}
#endif
}
}
}
// CRITICAL FIX: Handle 0-byte files that don't enter the while loop
if (bytesRead == 0 && fileBytesProcessed == 0 && extractSuccess) {
// This is a 0-byte file - update progress by 1 byte equivalent
totalBytesProcessed += 1;
// Update progress for 0-byte files
if (totalUncompressedSize > 0) {
newProgress = static_cast<int>((totalBytesProcessed * 100) / totalUncompressedSize);
if (newProgress > currentProgress && newProgress <= 100) {
currentProgress = newProgress;
unzipPercentage.store(currentProgress, std::memory_order_release);
#if USING_LOGGING_DIRECTIVE
if (currentProgress % 10 == 0) {
if (!disableLogging)
logMessage("Progress: " + std::to_string(currentProgress) + "% (0-byte file processed)");
}
#endif
}
}
}
// Check for read errors
if (bytesRead < 0) {
extractSuccess = false;
}
// Close current file handles
outputFile.close();
unzCloseCurrentFile(zipFile);
if (!extractSuccess) {
deleteFileOrDirectory(extractedFilePath);
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Failed to extract: " + fileName);
#endif
if (abortUnzip.load(std::memory_order_relaxed)) {
success = false;
break;
}
} else {
filesProcessed++;
}
// Move to next file
result = unzGoToNextFile(zipFile);
}
// Check final abort state
if (abortUnzip.load(std::memory_order_relaxed)) {
unzipPercentage.store(-1, std::memory_order_release);
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Extraction aborted by user");
#endif
abortUnzip.store(false, std::memory_order_release);
return false;
}
if (success && filesProcessed > 0) {
abortUnzip.store(false, std::memory_order_release);
unzipPercentage.store(100, std::memory_order_release);
#if USING_LOGGING_DIRECTIVE
if (!disableLogging) {
logMessage("Extraction completed: " + std::to_string(filesProcessed) + " files, " +
std::to_string(totalBytesProcessed) + " bytes");
}
#endif
return true;
} else {
abortUnzip.store(false, std::memory_order_release);
unzipPercentage.store(-1, std::memory_order_release);
return false;
}
}
}

View File

@@ -0,0 +1,506 @@
/********************************************************************************
* File: get_funcs.cpp
* Author: ppkantorski
* Description:
* This source file provides the implementations of functions declared in
* get_funcs.hpp. These functions are responsible for retrieving and handling
* data from the file system and JSON files, including parsing overlay module
* information, reading file contents, and accessing structured data used
* in the Ultrahand Overlay project.
*
* For the latest updates and contributions, visit the project's GitHub repository.
* (GitHub Repository: https://github.com/ppkantorski/Ultrahand-Overlay)
*
* Note: Please be aware that this notice cannot be altered or removed. It is a part
* of the project's documentation and must remain intact.
*
* Licensed under both GPLv2 and CC-BY-4.0
* Copyright (c) 2024 ppkantorski
********************************************************************************/
#include "get_funcs.hpp"
namespace ult {
/**
* @brief Reads the contents of a file and returns it as a string, normalizing line endings.
*
* @param filePath The path to the file to be read.
* @return The content of the file as a string with line endings normalized to '\n'.
*/
//std::string getFileContents(const std::string& filePath) {
// #if !USING_FSTREAM_DIRECTIVE
// FILE* file = fopen(filePath.c_str(), "rb");
// if (!file) {
// #if USING_LOGGING_DIRECTIVE
// logMessage("Failed to open file: " + filePath);
// #endif
// return "";
// }
//
// // Determine the file size
// fseek(file, 0, SEEK_END);
// long size = ftell(file);
// if (size <= 0) {
// fclose(file);
// return "";
// }
// fseek(file, 0, SEEK_SET);
//
// // Read the entire file into a string
// std::string content(size, '\0');
// if (fread(&content[0], 1, size, file) != static_cast<size_t>(size)) {
// #if USING_LOGGING_DIRECTIVE
// logMessage("Failed to read file: " + filePath);
// #endif
// fclose(file);
// return "";
// }
//
// fclose(file);
//
// #else
// std::ifstream file(filePath, std::ios::binary);
// if (!file) {
// #if USING_LOGGING_DIRECTIVE
// logMessage("Failed to open file: " + filePath);
// #endif
// return "";
// }
//
// // Determine the file size
// file.seekg(0, std::ios::end);
// std::streamsize size = file.tellg();
// if (size <= 0) {
// return "";
// }
// file.seekg(0, std::ios::beg);
//
// // Read the entire file into a string
// std::string content(size, '\0');
// if (!file.read(&content[0], size)) {
// #if USING_LOGGING_DIRECTIVE
// logMessage("Failed to read file: " + filePath);
// #endif
// return "";
// }
// #endif
//
// // Erase any carriage return characters (normalize line endings)
// content.erase(std::remove(content.begin(), content.end(), '\r'), content.end());
// return content;
//}
/**
* @brief Concatenates the provided directory and file names to form a destination path.
*
* @param destinationDir The directory where the file should be placed.
* @param fileName The name of the file.
* @return The destination path as a string.
*/
std::string getDestinationPath(const std::string& destinationDir,
const std::string& fileName)
{
// e.g. "foo/bar" + "/" + "baz.txt" → "foo/bar/baz.txt", but if destinationDir ended in '/',
// youd get "foo/bar//baz.txt" → collapse again:
std::string combined = destinationDir + "/" + fileName;
preprocessPath(combined);
return combined;
}
/**
* @brief Extracts the value part from a string line containing a key-value pair.
*
* @param line The string line containing a key-value pair (e.g., "key=value").
* @return The extracted value as a string. If no value is found, an empty string is returned.
*/
std::string getValueFromLine(const std::string& line) {
const size_t equalsPos = line.rfind('=');
if (equalsPos == std::string::npos || equalsPos + 1 >= line.size()) {
return "";
}
// OPTIMIZATION: Find trim boundaries directly - no temporary string
size_t start = equalsPos + 1;
size_t end = line.size() - 1;
// Skip leading whitespace
while (start <= end && (line[start] == ' ' || line[start] == '\t' ||
line[start] == '\n' || line[start] == '\r' ||
line[start] == '\f' || line[start] == '\v')) {
++start;
}
// Skip trailing whitespace
while (end >= start && (line[end] == ' ' || line[end] == '\t' ||
line[end] == '\n' || line[end] == '\r' ||
line[end] == '\f' || line[end] == '\v')) {
--end;
}
// Return trimmed substring directly
return (start <= end) ? line.substr(start, end - start + 1) : "";
}
/**
* @brief Extracts the name from a file path, including handling directories.
*
* @param path The file path from which to extract the name.
* @return The extracted name as a string. If the path indicates a directory, it extracts the last directory name.
* If the path is empty or no name is found, an empty string is returned.
*/
std::string getNameFromPath(const std::string& path) {
const size_t lastNonSlash = path.find_last_not_of('/');
if (lastNonSlash == std::string::npos) {
return ""; // All slashes, or empty string effectively
}
const size_t lastSlash = path.find_last_of('/', lastNonSlash);
if (lastSlash == std::string::npos) {
return path.substr(0, lastNonSlash + 1); // No slashes, the entire path is a filename
}
return path.substr(lastSlash + 1, lastNonSlash - lastSlash); // Standard case, efficiently handled
}
/**
* @brief Extracts the file name from a full file path.
*
* This function takes a filesystem path and returns only the file name,
* stripping away any directory paths that precede it.
*
* @param path The full path to the file.
* @return The file name extracted from the full path.
*/
std::string getFileName(const std::string& path) {
const size_t pos = path.find_last_of('/');
return (pos != std::string::npos) ? path.substr(pos + 1) : "";
}
/**
* @brief Extracts the name of the parent directory from a given file path at a specified level.
*
* @param path The file path from which to extract the parent directory name.
* @param level The level of the parent directory to extract (0 for immediate parent, 1 for grandparent, and so on).
* @return The name of the parent directory at the specified level.
*/
std::string getParentDirNameFromPath(const std::string& path, size_t level) {
if (path.empty()) return "";
// Start from the end of the string and move backwards to find the slashes
size_t endPos = path.find_last_not_of('/');
if (endPos == std::string::npos) return "";
size_t pos = path.rfind('/', endPos);
if (pos == std::string::npos || pos == 0) return "";
// Navigate up the specified number of levels
while (level-- > 0 && pos != std::string::npos) {
endPos = pos - 1;
pos = path.rfind('/', endPos);
if (pos == std::string::npos || pos == 0) return "";
}
size_t start = path.rfind('/', pos - 1);
if (start == std::string::npos) start = 0;
else start += 1;
// OPTIMIZATION 1: Use find_first_of instead of manual loop - much faster
const bool hasWhitespace = (path.find_first_of(" \t\n\r\f\v", start, pos - start) != std::string::npos);
if (hasWhitespace) {
// OPTIMIZATION 2: Pre-allocate exact size and build efficiently
const size_t dirNameLen = pos - start;
std::string result;
result.reserve(dirNameLen + 2); // +2 for quotes
result = '"';
result.append(path, start, dirNameLen);
result += '"';
return result;
} else {
return path.substr(start, pos - start);
}
}
/**
* @brief Extracts the parent directory path from a given file path.
*
* @param path The file path from which to extract the parent directory path.
* @return The parent directory path.
*/
std::string getParentDirFromPath(const std::string& path) {
const size_t lastSlash = path.find_last_of('/');
if (lastSlash != std::string::npos) {
return path.substr(0, lastSlash + 1);
}
return path;
}
/**
* @brief Gets a list of subdirectories in a directory.
*
* @param directoryPath The path of the directory to search.
* @return A vector of strings containing the names of subdirectories.
*/
std::vector<std::string> getSubdirectories(const std::string& directoryPath) {
std::vector<std::string> subdirectories;
std::unique_ptr<DIR, DirCloser> dir(opendir(directoryPath.c_str()));
if (!dir) return subdirectories;
struct dirent* entry;
while ((entry = readdir(dir.get())) != nullptr) {
const std::string entryName = entry->d_name;
// Skip . and ..
if (entryName == "." || entryName == "..") continue;
const std::string fullPath = directoryPath + "/" + entryName;
if (isDirectory(entry, fullPath)) {
subdirectories.emplace_back(entryName);
}
}
return subdirectories;
}
/**
* @brief Check if a directory entry is a directory (no caching).
* Fast path for known types, stat() only when necessary.
*/
inline bool isDirectory(struct dirent* entry, const std::string& path) {
// Fast path - most filesystems populate d_type correctly
if (entry->d_type == DT_DIR) {
return true;
} else if (entry->d_type != DT_UNKNOWN) {
return false; // DT_REG, DT_LNK, etc.
}
// Only stat when d_type is unknown (rare on modern filesystems)
struct stat entryStat;
return (stat(path.c_str(), &entryStat) == 0) && S_ISDIR(entryStat.st_mode);
}
/**
* @brief Iteratively retrieves a list of files from a directory.
*
* @param directoryPath The path of the directory to search.
* @return A vector of strings containing the paths of the files.
*/
std::vector<std::string> getFilesListFromDirectory(const std::string& directoryPath) {
std::vector<std::string> fileList;
std::vector<std::string> dirsToProcess;
// Initialize with the starting directory
dirsToProcess.emplace_back(directoryPath);
// Pre-allocate string buffer to avoid repeated allocations
std::string fullPath;
std::string currentDir;
size_t dirIndex = 0;
while (dirIndex < dirsToProcess.size()) {
currentDir = std::move(dirsToProcess[dirIndex]);
dirsToProcess[dirIndex].shrink_to_fit();
dirIndex++;
std::unique_ptr<DIR, DirCloser> dir(opendir(currentDir.c_str()));
if (!dir) continue;
// Cache directory path info
const bool needsSlash = currentDir.back() != '/';
struct dirent* entry;
while ((entry = readdir(dir.get())) != nullptr) {
const char* entryName = entry->d_name;
// Direct comparison without string creation
if (entryName[0] == '.' && (entryName[1] == '\0' || (entryName[1] == '.' && entryName[2] == '\0'))) {
continue;
}
// More efficient path building
fullPath.clear();
fullPath.assign(currentDir);
if (needsSlash) fullPath += '/';
fullPath += entryName;
if (entry->d_type == DT_REG) {
// Definitely a regular file
fileList.emplace_back(fullPath);
} else if (isDirectory(entry, fullPath)) {
// Add directory to processing queue
dirsToProcess.emplace_back(fullPath);
}
}
}
return fileList;
}
// Iterative function to handle wildcard directories and file patterns
void handleDirectory(const std::string& basePath,
const std::vector<std::string>& parts,
size_t partIndex,
std::vector<std::string>& results,
bool directoryOnly,
size_t maxLines) {
std::vector<std::pair<std::string, size_t>> stack;
stack.emplace_back(basePath, partIndex);
// Pre-declare strings to avoid repeated allocations
std::string fullPath;
std::string result;
std::string currentPath;
//fullPath.reserve(512);
//result.reserve(512);
//currentPath.reserve(512);
struct stat st;
bool isDir;
//std::string pathRef;
size_t currentPartIndex;
while (!stack.empty()) {
if (maxLines > 0 && results.size() >= maxLines) return;
std::tie(currentPath, currentPartIndex) = stack.back();
stack.pop_back();
// Copy once to avoid repeated access
//currentPath = pathRef;
if (currentPartIndex >= parts.size()) continue;
DIR* dirPtr = opendir(currentPath.c_str());
if (!dirPtr) continue;
std::unique_ptr<DIR, DirCloser> dir(dirPtr);
const std::string& pattern = parts[currentPartIndex];
const bool isLastPart = (currentPartIndex == parts.size() - 1);
const bool needsSlash = currentPath.back() != '/';
// Pre-calculate base path for efficiency
//const size_t basePathLen = currentPath.length();
struct dirent* entry;
while ((entry = readdir(dir.get())) != nullptr) {
if (maxLines > 0 && results.size() >= maxLines) return;
const char* name = entry->d_name;
if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) continue;
if (fnmatch(pattern.c_str(), name, FNM_NOESCAPE) != 0) continue;
if (entry->d_type != DT_UNKNOWN) {
isDir = (entry->d_type == DT_DIR);
} else {
// More efficient path building for stat check
fullPath.clear();
fullPath.assign(currentPath);
if (needsSlash) fullPath += '/';
fullPath += name;
isDir = (stat(fullPath.c_str(), &st) == 0) && S_ISDIR(st.st_mode);
}
if (isLastPart) {
if (!directoryOnly || isDir) {
// More efficient result building
result.clear();
result.assign(currentPath);
if (needsSlash) result += '/';
result += name;
if (isDir) result += '/';
results.emplace_back(std::move(result));
if (maxLines > 0 && results.size() >= maxLines) return;
}
} else if (isDir) {
// More efficient path building for stack
fullPath.clear();
fullPath.assign(currentPath);
if (needsSlash) fullPath += '/';
fullPath += name;
stack.emplace_back(std::move(fullPath), currentPartIndex + 1);
}
}
}
}
/**
* @brief Gets a list of files and folders based on a wildcard pattern.
*
* This function searches for files and folders in a directory that match the
* specified wildcard pattern.
*
* @param pathPattern The wildcard pattern to match files and folders.
* @return A vector of strings containing the paths of matching files and folders.
*/
std::vector<std::string> getFilesListByWildcards(const std::string& pathPattern, size_t maxLines) {
std::vector<std::string> results;
if (pathPattern.empty()) return results;
// Disallow any `/**/` or ending with `/**`
if (pathPattern.find("**") != std::string::npos || pathPattern.find("*null") != std::string::npos || pathPattern.find("null*") != std::string::npos) {
return results; // Exclude invalid patterns
}
const bool directoryOnly = pathPattern.back() == '/';
const size_t prefixEnd = pathPattern.find(":/");
if (prefixEnd == std::string::npos) return results;
const std::string basePath = pathPattern.substr(0, prefixEnd + 2);
std::vector<std::string> parts;
size_t start = prefixEnd + 2;
size_t pos = start;
const size_t pathLen = pathPattern.length();
while (pos <= pathLen) {
if (pos == pathLen || pathPattern[pos] == '/') {
if (pos > start) {
parts.emplace_back(pathPattern.data() + start, pos - start);
}
start = pos + 1;
}
++pos;
}
if (start < pathLen && !directoryOnly) {
parts.emplace_back(pathPattern.data() + start, pathLen - start);
}
// Extra: check parsed parts to disallow "**"
for (size_t i = 0; i + 1 < parts.size(); ++i) {
if (parts[i] == "**" && parts[i + 1] == "**") {
return results; // invalid, exclude
}
}
if (!parts.empty()) {
handleDirectory(basePath, parts, 0, results, directoryOnly, maxLines);
}
return results;
}
}

View File

@@ -0,0 +1,178 @@
/********************************************************************************
* File: global_vars.cpp
* Author: ppkantorski
* Description:
* This source file provides the definitions of global constants and paths used
* throughout the Ultrahand Overlay project. These constants are essential for
* file management and configuration settings within the application.
*
* For the latest updates and contributions, visit the project's GitHub repository:
* GitHub Repository: https://github.com/ppkantorski/Ultrahand-Overlay
*
* Note: This notice is integral to the project's documentation and must not be
* altered or removed.
*
* Licensed under both GPLv2 and CC-BY-4.0
* Copyright (c) 2024 ppkantorski
********************************************************************************/
#include "global_vars.hpp"
namespace ult {
// Base paths
const std::string ROOT_PATH = "sdmc:/";
const std::string BASE_CONFIG_PATH = ROOT_PATH + "config/ultrahand/";
const std::string TESLA_CONFIG_PATH = ROOT_PATH + "config/tesla/";
const std::string SWITCH_PATH = ROOT_PATH + "switch/";
// Filenames
CONSTEXPR_STRING std::string CONFIG_FILENAME = "config.ini";
const std::string BOOT_PACKAGE_FILENAME = "boot_package.ini";
const std::string EXIT_PACKAGE_FILENAME = "exit_package.ini";
const std::string PACKAGE_FILENAME = "package.ini";
const std::string THEME_FILENAME = "theme.ini";
const std::string WALLPAPER_FILENAME = "wallpaper.rgba";
const std::string FUSE_FILENAME = "fuse.ini";
const std::string OVERLAYS_INI_FILENAME = "overlays.ini";
const std::string PACKAGES_INI_FILENAME = "packages.ini";
const std::string NOTIFICATIONS_FLAG_FILENAME = "NOTIFICATIONS.flag";
// Project names
CONSTEXPR_STRING std::string ULTRAHAND_PROJECT_NAME = "ultrahand";
CONSTEXPR_STRING std::string CAPITAL_ULTRAHAND_PROJECT_NAME = "Ultrahand";
CONSTEXPR_STRING std::string SPLIT_PROJECT_NAME_1 = "Ultra";
CONSTEXPR_STRING std::string SPLIT_PROJECT_NAME_2 = "hand";
// Paths
const std::string SETTINGS_PATH = BASE_CONFIG_PATH;
const std::string ULTRAHAND_CONFIG_INI_PATH = BASE_CONFIG_PATH + CONFIG_FILENAME;
const std::string TESLA_CONFIG_INI_PATH = TESLA_CONFIG_PATH + CONFIG_FILENAME;
const std::string LANG_PATH = BASE_CONFIG_PATH + "lang/";
const std::string THEMES_PATH = BASE_CONFIG_PATH + "themes/";
const std::string WALLPAPERS_PATH = BASE_CONFIG_PATH + "wallpapers/";
const std::string FLAGS_PATH = BASE_CONFIG_PATH + "flags/";
const std::string NOTIFICATIONS_PATH = BASE_CONFIG_PATH + "notifications/";
const std::string PAYLOADS_PATH = BASE_CONFIG_PATH + "payloads/";
const std::string HB_APPSTORE_JSON = SWITCH_PATH + ".appstore/.get/packages/UltrahandOverlay/info.json";
std::string THEME_CONFIG_INI_PATH = BASE_CONFIG_PATH + THEME_FILENAME;
std::string WALLPAPER_PATH = BASE_CONFIG_PATH + WALLPAPER_FILENAME;
const std::string DOWNLOADS_PATH = BASE_CONFIG_PATH + "downloads/";
const std::string EXPANSION_PATH = BASE_CONFIG_PATH + "expansion/";
const std::string FUSE_DATA_INI_PATH = BASE_CONFIG_PATH + FUSE_FILENAME;
const std::string PACKAGE_PATH = SWITCH_PATH + ".packages/";
const std::string OVERLAY_PATH = SWITCH_PATH + ".overlays/";
const std::string OVERLAYS_INI_FILEPATH = BASE_CONFIG_PATH + OVERLAYS_INI_FILENAME;
const std::string PACKAGES_INI_FILEPATH = BASE_CONFIG_PATH + PACKAGES_INI_FILENAME;
const std::string NOTIFICATIONS_FLAG_FILEPATH = FLAGS_PATH + NOTIFICATIONS_FLAG_FILENAME;
// Protected files
const std::set<std::string> PROTECTED_FILES = {
ROOT_PATH + "atmosphere/package3",
ROOT_PATH + "atmosphere/stratosphere.romfs"
};
// GitHub URLs
const std::string GITHUB_BASE_URL = "https://github.com/ppkantorski/";
const std::string GITHUB_RAW_BASE_URL = "https://raw.githubusercontent.com/ppkantorski/";
const std::string ULTRAHAND_REPO_URL = GITHUB_BASE_URL + "Ultrahand-Overlay/";
const std::string INCLUDED_THEME_FOLDER_URL = GITHUB_RAW_BASE_URL + "Ultrahand-Overlay/main/themes/";
const std::string LATEST_RELEASE_INFO_URL = GITHUB_RAW_BASE_URL + "Ultrahand-Overlay/main/RELEASE.ini";
const std::string NX_OVLLOADER_ZIP_URL = GITHUB_BASE_URL + "nx-ovlloader/releases/latest/download/nx-ovlloader.zip";
const std::string NX_OVLLOADER_PLUS_ZIP_URL = GITHUB_BASE_URL + "nx-ovlloader/releases/latest/download/nx-ovlloader+.zip";
const std::string OLD_NX_OVLLOADER_ZIP_URL = GITHUB_BASE_URL + "nx-ovlloader/releases/download/v1.0.8/nx-ovlloader.zip";
const std::string OLD_NX_OVLLOADER_PLUS_ZIP_URL = GITHUB_BASE_URL + "nx-ovlloader/releases/download/v1.0.8/nx-ovlloader+.zip";
const std::string UPDATER_PAYLOAD_URL = GITHUB_RAW_BASE_URL + "Ultrahand-Overlay/main/payloads/ultrahand_updater.bin";
// Launch options
const std::string LAUNCH_ARGS_STR = "launch_args";
const std::string USE_LAUNCH_ARGS_STR = "use_launch_args";
const std::string USE_QUICK_LAUNCH_STR = "use_quick_launch";
const std::string USE_BOOT_PACKAGE_STR = "use_boot_package";
const std::string USE_EXIT_PACKAGE_STR = "use_exit_package";
const std::string USE_LOGGING_STR = "use_logging";
// Combos
CONSTEXPR_STRING std::string TESLA_COMBO_STR = "L+DDOWN+RS";
CONSTEXPR_STRING std::string ULTRAHAND_COMBO_STR = "ZL+ZR+DDOWN";
// System / mode strings
CONSTEXPR_STRING std::string FUSE_STR = "fuse";
CONSTEXPR_STRING std::string TESLA_STR = "tesla";
CONSTEXPR_STRING std::string ERISTA_STR = "erista";
CONSTEXPR_STRING std::string MARIKO_STR = "mariko";
CONSTEXPR_STRING std::string KEY_COMBO_STR = "key_combo";
CONSTEXPR_STRING std::string DEFAULT_LANG_STR = "default_lang";
// Generic strings
CONSTEXPR_STRING std::string LIST_STR = "list";
CONSTEXPR_STRING std::string LIST_FILE_STR = "list_file";
CONSTEXPR_STRING std::string JSON_STR = "json";
CONSTEXPR_STRING std::string JSON_FILE_STR = "json_file";
CONSTEXPR_STRING std::string INI_FILE_STR = "ini_file";
CONSTEXPR_STRING std::string HEX_FILE_STR = "hex_file";
CONSTEXPR_STRING std::string PACKAGE_STR = "package";
CONSTEXPR_STRING std::string PACKAGES_STR = "packages";
CONSTEXPR_STRING std::string OVERLAY_STR = "overlay";
CONSTEXPR_STRING std::string OVERLAYS_STR = "overlays";
const std::string IN_OVERLAY_STR = "in_overlay";
const std::string IN_HIDDEN_OVERLAY_STR = "in_hidden_overlay";
const std::string IN_HIDDEN_PACKAGE_STR = "in_hidden_package";
CONSTEXPR_STRING std::string FILE_STR = "file";
CONSTEXPR_STRING std::string SYSTEM_STR = "system";
CONSTEXPR_STRING std::string MODE_STR = "mode";
CONSTEXPR_STRING std::string GROUPING_STR = "grouping";
CONSTEXPR_STRING std::string FOOTER_STR = "footer";
CONSTEXPR_STRING std::string TOGGLE_STR = "toggle";
CONSTEXPR_STRING std::string LEFT_STR = "left";
CONSTEXPR_STRING std::string RIGHT_STR = "right";
CONSTEXPR_STRING std::string CENTER_STR = "center";
CONSTEXPR_STRING std::string HIDE_STR = "hide";
CONSTEXPR_STRING std::string STAR_STR = "star";
CONSTEXPR_STRING std::string PRIORITY_STR = "priority";
CONSTEXPR_STRING std::string ON_STR = "on";
CONSTEXPR_STRING std::string OFF_STR = "off";
CONSTEXPR_STRING std::string CAPITAL_ON_STR = "On";
CONSTEXPR_STRING std::string CAPITAL_OFF_STR = "Off";
CONSTEXPR_STRING std::string TRUE_STR = "true";
CONSTEXPR_STRING std::string FALSE_STR = "false";
CONSTEXPR_STRING std::string GLOBAL_STR = "global";
CONSTEXPR_STRING std::string DEFAULT_STR = "default";
CONSTEXPR_STRING std::string SLOT_STR = "slot";
CONSTEXPR_STRING std::string OPTION_STR = "option";
CONSTEXPR_STRING std::string FORWARDER_STR = "forwarder";
CONSTEXPR_STRING std::string TEXT_STR = "text";
CONSTEXPR_STRING std::string TABLE_STR = "table";
CONSTEXPR_STRING std::string TRACKBAR_STR = "trackbar";
CONSTEXPR_STRING std::string STEP_TRACKBAR_STR = "step_trackbar";
const std::string NAMED_STEP_TRACKBAR_STR = "named_step_trackbar";
CONSTEXPR_STRING std::string NULL_STR = "null";
CONSTEXPR_STRING std::string THEME_STR = "theme";
CONSTEXPR_STRING std::string NOT_AVAILABLE_STR = "Not available";
CONSTEXPR_STRING std::string MEMORY_STR = "memory";
// Pre-defined symbols
CONSTEXPR_STRING std::string OPTION_SYMBOL = "\u22EF";
CONSTEXPR_STRING std::string DROPDOWN_SYMBOL = "\uE14A";
CONSTEXPR_STRING std::string CHECKMARK_SYMBOL = "\uE14B";
CONSTEXPR_STRING std::string CROSSMARK_SYMBOL = "\uE14C";
CONSTEXPR_STRING std::string DOWNLOAD_SYMBOL = "\u2193";
CONSTEXPR_STRING std::string UNZIP_SYMBOL = "\u2191";
CONSTEXPR_STRING std::string COPY_SYMBOL = "\u2192";
CONSTEXPR_STRING std::string INPROGRESS_SYMBOL = "\u25CF";
CONSTEXPR_STRING std::string STAR_SYMBOL = "\u2605";
CONSTEXPR_STRING std::string DIVIDER_SYMBOL = "";
const std::vector<std::string> THROBBER_SYMBOLS = {"", "", "", "", "", "", "", ""};
// Atomic variables for progress tracking
std::atomic<int> displayPercentage(0); // for interpreter percentage progress
void resetPercentages() {
displayPercentage.store(-1, std::memory_order_release);
downloadPercentage.store(-1, std::memory_order_release);
unzipPercentage.store(-1, std::memory_order_release);
copyPercentage.store(-1, std::memory_order_release);
}
} // namespace ult

View File

@@ -0,0 +1,881 @@
/********************************************************************************
* File: hex_funcs.cpp
* Author: ppkantorski
* Description:
* This source file implements the functions declared in hex_funcs.hpp.
* These functions provide support for manipulating hexadecimal data,
* including conversions between ASCII and hexadecimal strings,
* locating specific hex patterns within files, and editing file contents
* at hex offsets.
*
* For the latest updates and contributions, visit the project's GitHub repository.
* (GitHub Repository: https://github.com/ppkantorski/Ultrahand-Overlay)
*
* Note: Please be aware that this notice cannot be altered or removed. It is a part
* of the project's documentation and must remain intact.
*
* Licensed under both GPLv2 and CC-BY-4.0
* Copyright (c) 2024 ppkantorski
********************************************************************************/
#include "hex_funcs.hpp"
namespace ult {
size_t HEX_BUFFER_SIZE = 4096;//65536/4;
// Thread-safe cache and file operation mutexes
std::shared_mutex cacheMutex; // Allows multiple readers, single writer
std::mutex fileWriteMutex; // Protects file write operations
// For improving the speed of hexing consecutively with the same file and asciiPattern.
std::unordered_map<std::string, std::string> hexSumCache;
/**
* @brief Thread-safe cache management functions
*/
void clearHexSumCache() {
std::lock_guard<std::shared_mutex> writeLock(cacheMutex);
//hexSumCache.clear();
hexSumCache = {};
}
size_t getHexSumCacheSize() {
std::shared_lock<std::shared_mutex> readLock(cacheMutex);
return hexSumCache.size();
}
/**
* @brief Converts an ASCII string to a hexadecimal string.
*
* This function takes an ASCII string as input and converts it into a hexadecimal string.
*
* @param asciiStr The ASCII string to convert.
* @return The corresponding hexadecimal string.
*/
// Function to convert ASCII string to Hex string
std::string asciiToHex(const std::string& asciiStr) {
std::string hexStr;
//hexStr.reserve(asciiStr.length() * 2); // Reserve space for the hexadecimal string
for (unsigned char c : asciiStr) {
hexStr.push_back(hexLookup[c >> 4]); // High nibble
hexStr.push_back(hexLookup[c & 0x0F]); // Low nibble
}
return hexStr;
}
/**
* @brief Converts a decimal string to a fixed-width hexadecimal string.
*
* @param decimalStr The decimal string to convert.
* @param byteGroupSize The number of hex digits to output (must be even for byte alignment).
* @return Hex string of exactly 'byteGroupSize' digits, or empty string if value doesn't fit.
*/
std::string decimalToHex(const std::string& decimalStr, int byteGroupSize) {
const int decimalValue = ult::stoi(decimalStr);
if (decimalValue < 0 || byteGroupSize <= 0 || (byteGroupSize % 2) != 0) {
// Invalid input: negative number, or byteGroupSize <= 0, or odd byteGroupSize
return "";
}
// Special case: zero
if (decimalValue == 0) {
return std::string(byteGroupSize, '0');
}
// Convert decimalValue to hex (uppercase, minimal length)
std::string hex;
int tempValue = decimalValue;
int remainder;
char hexChar;
while (tempValue > 0) {
remainder = tempValue % 16;
hexChar = (remainder < 10) ? ('0' + remainder) : ('A' + remainder - 10);
hex.insert(hex.begin(), hexChar);
tempValue /= 16;
}
// Ensure hex length is even by adding leading zero if needed
if (hex.length() % 2 != 0) {
hex.insert(hex.begin(), '0');
}
// Minimum size needed to fit hex string
const size_t hexLen = hex.length();
// Adjust minimum byteGroupSize to be at least hexLen
size_t minByteGroupSize = std::max(static_cast<size_t>(byteGroupSize), hexLen);
// If byteGroupSize was too small, adjust to hex length (must be even)
if (minByteGroupSize % 2 != 0) {
minByteGroupSize++;
}
// If minByteGroupSize is less than hex length, number doesn't fit
if (minByteGroupSize < hexLen) {
return ""; // can't fit
}
// Pad with leading zeros to match minByteGroupSize
if (hexLen < minByteGroupSize) {
hex.insert(hex.begin(), minByteGroupSize - hexLen, '0');
}
return hex;
}
/**
* @brief Converts a hexadecimal string to a decimal string.
*
* This function takes a hexadecimal string as input and converts it into a decimal string.
*
* @param hexStr The hexadecimal string to convert.
* @return The corresponding decimal string.
*/
std::string hexToDecimal(const std::string& hexStr) {
// Convert hexadecimal string to integer
int decimalValue = 0;
const size_t len = hexStr.length();
char hexChar;
int value;
// Iterate over each character in the hexadecimal string
for (size_t i = 0; i < len; ++i) {
hexChar = hexStr[i];
//int value;
// Convert hex character to its decimal value
if (hexChar >= '0' && hexChar <= '9') {
value = hexChar - '0';
} else if (hexChar >= 'A' && hexChar <= 'F') {
value = 10 + (hexChar - 'A');
} else if (hexChar >= 'a' && hexChar <= 'f') {
value = 10 + (hexChar - 'a');
} else {
break;
//throw std::invalid_argument("Invalid hexadecimal character");
}
// Update the decimal value
decimalValue = decimalValue * 16 + value;
}
// Convert the decimal value to a string
return ult::to_string(decimalValue);
}
std::string hexToReversedHex(const std::string& hexadecimal, int order) {
// Reverse the hexadecimal string in groups of order
std::string reversedHex;
for (int i = hexadecimal.length() - order; i >= 0; i -= order) {
reversedHex += hexadecimal.substr(i, order);
}
return reversedHex;
}
/**
* @brief Converts a decimal string to a reversed hexadecimal string.
*
* This function takes a decimal string as input, converts it into a hexadecimal
* string, and reverses the resulting hexadecimal string in groups of byteGroupSize.
*
* @param decimalStr The decimal string to convert.
* @param byteGroupSize The grouping byteGroupSize for reversing the hexadecimal string.
* @return The reversed hexadecimal string.
*/
std::string decimalToReversedHex(const std::string& decimalStr, int byteGroupSize) {
//std::string hexadecimal = decimalToHex(decimalStr, byteGroupSize);
// Reverse the hexadecimal string in groups of byteGroupSize
//std::string reversedHex;
//for (int i = hexadecimal.length() - byteGroupSize; i >= 0; i -= byteGroupSize) {
// reversedHex += hexadecimal.substr(i, byteGroupSize);
//}
return hexToReversedHex(decimalToHex(decimalStr, byteGroupSize));
}
/**
* @brief Finds the offsets of hexadecimal data in a file.
*
* This function searches for occurrences of hexadecimal data in a binary file
* and returns the file offsets where the data is found.
*
* @param filePath The path to the binary file.
* @param hexData The hexadecimal data to search for.
* @return A vector of strings containing the file offsets where the data is found.
*/
std::vector<std::string> findHexDataOffsets(const std::string& filePath, const std::string& hexData) {
std::vector<std::string> offsets;
#if !USING_FSTREAM_DIRECTIVE
FILE* file = fopen(filePath.c_str(), "rb");
if (!file) {
return offsets;
}
fseek(file, 0, SEEK_END);
const size_t fileSize = ftell(file);
fseek(file, 0, SEEK_SET);
std::vector<unsigned char> binaryData;
if (hexData.length() % 2 != 0) {
fclose(file);
return offsets;
}
const size_t hexLen = hexData.length();
binaryData.resize(hexLen / 2);
const unsigned char* hexPtr = reinterpret_cast<const unsigned char*>(hexData.c_str());
// Unrolled hex conversion loop
size_t i = 0;
for (; i + 4 <= hexLen; i += 4) {
binaryData[i/2] = (hexTable[hexPtr[i]] << 4) | hexTable[hexPtr[i + 1]];
binaryData[i/2 + 1] = (hexTable[hexPtr[i + 2]] << 4) | hexTable[hexPtr[i + 3]];
}
// Handle remaining bytes
for (; i < hexLen; i += 2) {
binaryData[i/2] = (hexTable[hexPtr[i]] << 4) | hexTable[hexPtr[i + 1]];
}
// Optimized search variables
const unsigned char* patternPtr = binaryData.data();
const size_t patternLen = binaryData.size();
const unsigned char firstByte = patternPtr[0];
std::vector<unsigned char> buffer(HEX_BUFFER_SIZE);
size_t bytesRead = 0;
size_t offset = 0;
while ((bytesRead = fread(buffer.data(), 1, HEX_BUFFER_SIZE, file)) > 0) {
const unsigned char* bufPtr = buffer.data();
// Optimized search with first-byte filtering and loop unrolling
i = 0;
const size_t searchEnd = bytesRead;
// Process 4 bytes at a time for better cache usage
for (; i + 4 <= searchEnd; i += 4) {
// Check 4 positions at once
if (bufPtr[i] == firstByte) {
if (offset + i + patternLen <= fileSize &&
memcmp(bufPtr + i, patternPtr, patternLen) == 0) {
offsets.emplace_back(ult::to_string(offset + i));
}
}
if (bufPtr[i + 1] == firstByte) {
if (offset + i + 1 + patternLen <= fileSize &&
memcmp(bufPtr + i + 1, patternPtr, patternLen) == 0) {
offsets.emplace_back(ult::to_string(offset + i + 1));
}
}
if (bufPtr[i + 2] == firstByte) {
if (offset + i + 2 + patternLen <= fileSize &&
memcmp(bufPtr + i + 2, patternPtr, patternLen) == 0) {
offsets.emplace_back(ult::to_string(offset + i + 2));
}
}
if (bufPtr[i + 3] == firstByte) {
if (offset + i + 3 + patternLen <= fileSize &&
memcmp(bufPtr + i + 3, patternPtr, patternLen) == 0) {
offsets.emplace_back(ult::to_string(offset + i + 3));
}
}
}
// Handle remaining bytes
for (; i < searchEnd; ++i) {
if (bufPtr[i] == firstByte) {
if (offset + i + patternLen <= fileSize &&
memcmp(bufPtr + i, patternPtr, patternLen) == 0) {
offsets.emplace_back(ult::to_string(offset + i));
}
}
}
offset += bytesRead;
}
fclose(file);
#else
std::ifstream file(filePath, std::ios::binary);
if (!file.is_open()) {
return offsets;
}
file.seekg(0, std::ios::end);
const size_t fileSize = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<unsigned char> binaryData;
if (hexData.length() % 2 != 0) {
file.close();
return offsets;
}
const size_t hexLen = hexData.length();
binaryData.resize(hexLen / 2);
const unsigned char* hexPtr = reinterpret_cast<const unsigned char*>(hexData.c_str());
size_t i = 0;
for (; i + 4 <= hexLen; i += 4) {
binaryData[i/2] = (hexTable[hexPtr[i]] << 4) | hexTable[hexPtr[i + 1]];
binaryData[i/2 + 1] = (hexTable[hexPtr[i + 2]] << 4) | hexTable[hexPtr[i + 3]];
}
for (; i < hexLen; i += 2) {
binaryData[i/2] = (hexTable[hexPtr[i]] << 4) | hexTable[hexPtr[i + 1]];
}
const unsigned char* patternPtr = binaryData.data();
const size_t patternLen = binaryData.size();
const unsigned char firstByte = patternPtr[0];
std::vector<unsigned char> buffer(HEX_BUFFER_SIZE);
size_t bytesRead = 0;
size_t offset = 0;
while (file.read(reinterpret_cast<char*>(buffer.data()), HEX_BUFFER_SIZE) || file.gcount() > 0) {
bytesRead = file.gcount();
const unsigned char* bufPtr = buffer.data();
// Same optimized search as FILE* version
i = 0;
const size_t searchEnd = bytesRead;
for (; i + 4 <= searchEnd; i += 4) {
if (bufPtr[i] == firstByte) {
if (offset + i + patternLen <= fileSize &&
memcmp(bufPtr + i, patternPtr, patternLen) == 0) {
offsets.emplace_back(ult::to_string(offset + i));
}
}
if (bufPtr[i + 1] == firstByte) {
if (offset + i + 1 + patternLen <= fileSize &&
memcmp(bufPtr + i + 1, patternPtr, patternLen) == 0) {
offsets.emplace_back(ult::to_string(offset + i + 1));
}
}
if (bufPtr[i + 2] == firstByte) {
if (offset + i + 2 + patternLen <= fileSize &&
memcmp(bufPtr + i + 2, patternPtr, patternLen) == 0) {
offsets.emplace_back(ult::to_string(offset + i + 2));
}
}
if (bufPtr[i + 3] == firstByte) {
if (offset + i + 3 + patternLen <= fileSize &&
memcmp(bufPtr + i + 3, patternPtr, patternLen) == 0) {
offsets.emplace_back(ult::to_string(offset + i + 3));
}
}
}
for (; i < searchEnd; ++i) {
if (bufPtr[i] == firstByte) {
if (offset + i + patternLen <= fileSize &&
memcmp(bufPtr + i, patternPtr, patternLen) == 0) {
offsets.emplace_back(ult::to_string(offset + i));
}
}
}
offset += bytesRead;
}
file.close();
#endif
return offsets;
}
/**
* @brief Edits hexadecimal data in a file at a specified offset.
*
* This function opens a binary file, seeks to a specified offset, and replaces
* the data at that offset with the provided hexadecimal data.
*
* @param filePath The path to the binary file.
* @param offsetStr The offset in the file to perform the edit.
* @param hexData The hexadecimal data to replace at the offset.
*/
void hexEditByOffset(const std::string& filePath, const std::string& offsetStr, const std::string& hexData) {
// Lock file writes to prevent concurrent modifications to the same file
std::lock_guard<std::mutex> fileWriteLock(fileWriteMutex);
const std::streampos offset = std::stoll(offsetStr);
#if !USING_FSTREAM_DIRECTIVE
// Open the file for both reading and writing in binary mode
FILE* file = fopen(filePath.c_str(), "rb+");
if (!file) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Failed to open the file.");
#endif
return;
}
// Retrieve the file size
fseek(file, 0, SEEK_END);
const std::streampos fileSize = ftell(file);
fseek(file, 0, SEEK_SET);
if (offset >= fileSize) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Offset exceeds file size.");
#endif
fclose(file);
return;
}
// Convert the hex string to binary data
std::vector<unsigned char> binaryData(hexData.length() / 2);
std::string byteString;
for (size_t i = 0, j = 0; i < hexData.length(); i += 2, ++j) {
byteString = hexData.substr(i, 2);
binaryData[j] = static_cast<unsigned char>(ult::stoi(byteString, nullptr, 16));
}
// Move to the specified offset and write the binary data directly to the file
fseek(file, offset, SEEK_SET);
const size_t bytesWritten = fwrite(binaryData.data(), sizeof(unsigned char), binaryData.size(), file);
if (bytesWritten != binaryData.size()) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Failed to write data to the file.");
#endif
fclose(file);
return;
}
fclose(file);
#else
// Open the file for both reading and writing in binary mode
std::fstream file(filePath, std::ios::binary | std::ios::in | std::ios::out);
if (!file.is_open()) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Failed to open the file.");
#endif
return;
}
// Retrieve the file size
file.seekg(0, std::ios::end);
const std::streampos fileSize = file.tellg();
file.seekg(0, std::ios::beg);
if (offset >= fileSize) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Offset exceeds file size.");
#endif
return;
}
// Convert the hex string to binary data
std::vector<unsigned char> binaryData(hexData.length() / 2);
std::string byteString;
for (size_t i = 0, j = 0; i < hexData.length(); i += 2, ++j) {
byteString = hexData.substr(i, 2);
binaryData[j] = static_cast<unsigned char>(ult::stoi(byteString, nullptr, 16));
}
// Move to the specified offset and write the binary data directly to the file
file.seekp(offset);
file.write(reinterpret_cast<const char*>(binaryData.data()), binaryData.size());
if (!file) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Failed to write data to the file.");
#endif
return;
}
file.close();
#endif
}
/**
* @brief Edits a specific offset in a file with custom hexadecimal data.
*
* This function searches for a custom pattern in the file and calculates a new offset
* based on user-provided offsetStr and the found pattern. It then replaces the data
* at the calculated offset with the provided hexadecimal data.
*
* @param filePath The path to the binary file.
* @param offsetStr The user-provided offset for the edit.
* @param customPattern The custom pattern to search for in the file.
* @param hexDataReplacement The hexadecimal data to replace at the calculated offset.
* @param occurrence The occurrence/index of the data to replace (default is "0" to replace all occurrences).
*/
void hexEditByCustomOffset(const std::string& filePath, const std::string& customAsciiPattern, const std::string& offsetStr, const std::string& hexDataReplacement, size_t occurrence) {
// Create a cache key based on filePath and customAsciiPattern
const std::string cacheKey = filePath + '?' + customAsciiPattern + '?' + ult::to_string(occurrence);
int hexSum = -1;
// Thread-safe cache access
{
std::shared_lock<std::shared_mutex> readLock(cacheMutex);
const auto cachedResult = hexSumCache.find(cacheKey);
if (cachedResult != hexSumCache.end()) {
hexSum = ult::stoi(cachedResult->second);
}
}
if (hexSum == -1) {
std::string customHexPattern;
if (customAsciiPattern[0] == '#') {
// remove #
customHexPattern = customAsciiPattern.substr(1);
} else {
// Convert custom ASCII pattern to a custom hex pattern
customHexPattern = asciiToHex(customAsciiPattern);
}
// Find hex data offsets in the file
const std::vector<std::string> offsets = findHexDataOffsets(filePath, customHexPattern);
if (!offsets.empty()) {
hexSum = ult::stoi(offsets[occurrence]);
// Thread-safe cache write
{
std::lock_guard<std::shared_mutex> writeLock(cacheMutex);
hexSumCache[cacheKey] = ult::to_string(hexSum);
}
} else {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Offset not found.");
#endif
return;
}
}
if (hexSum != -1) {
// Calculate the total offset to seek in the file
//int sum = hexSum + ult::stoi(offsetStr);
hexEditByOffset(filePath, ult::to_string(hexSum + ult::stoi(offsetStr)), hexDataReplacement);
} else {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Failed to find " + customAsciiPattern + ".");
#endif
}
}
/**
* @brief Finds and replaces hexadecimal data in a file.
*
* This function searches for occurrences of hexadecimal data in a binary file
* and replaces them with a specified hexadecimal replacement data.
*
* @param filePath The path to the binary file.
* @param hexDataToReplace The hexadecimal data to search for and replace.
* @param hexDataReplacement The hexadecimal data to replace with.
* @param occurrence The occurrence/index of the data to replace (default is "0" to replace all occurrences).
*/
void hexEditFindReplace(const std::string& filePath, const std::string& hexDataToReplace, const std::string& hexDataReplacement, size_t occurrence) {
const std::vector<std::string> offsetStrs = findHexDataOffsets(filePath, hexDataToReplace);
if (!offsetStrs.empty()) {
if (occurrence == 0) {
// Replace all occurrences
for (const std::string& offsetStr : offsetStrs) {
//logMessage("offsetStr: "+offsetStr);
//logMessage("hexDataReplacement: "+hexDataReplacement);
hexEditByOffset(filePath, offsetStr, hexDataReplacement);
}
} else {
// Convert the occurrence string to an integer
if (occurrence > 0 && occurrence <= offsetStrs.size()) {
// Replace the specified occurrence/index
//std::string offsetStr = offsetStrs[occurrence - 1];
//logMessage("offsetStr: "+offsetStr);
//logMessage("hexDataReplacement: "+hexDataReplacement);
hexEditByOffset(filePath, offsetStrs[occurrence - 1], hexDataReplacement);
} else {
// Invalid occurrence/index specified
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Invalid hex occurrence/index specified.");
#endif
}
}
//std::cout << "Hex data replaced successfully." << std::endl;
}
}
/**
* @brief Finds and replaces hexadecimal data in a file.
*
* This function searches for occurrences of hexadecimal data in a binary file
* and replaces them with a specified hexadecimal replacement data.
*
* @param filePath The path to the binary file.
* @param hexDataToReplace The hexadecimal data to search for and replace.
* @param hexDataReplacement The hexadecimal data to replace with.
* @param occurrence The occurrence/index of the data to replace (default is "0" to replace all occurrences).
*/
std::string parseHexDataAtCustomOffset(const std::string& filePath, const std::string& customAsciiPattern,
const std::string& offsetStr, size_t length, size_t occurrence) {
const std::string cacheKey = filePath + '?' + customAsciiPattern + '?' + ult::to_string(occurrence);
int hexSum = -1;
// Thread-safe cache read
{
std::shared_lock<std::shared_mutex> readLock(cacheMutex);
const auto cachedResult = hexSumCache.find(cacheKey);
if (cachedResult != hexSumCache.end()) {
hexSum = ult::stoi(cachedResult->second);
}
}
if (hexSum == -1) {
const std::string customHexPattern = asciiToHex(customAsciiPattern);
const std::vector<std::string> offsets = findHexDataOffsets(filePath, customHexPattern);
if (!offsets.empty() && offsets.size() > occurrence) {
hexSum = ult::stoi(offsets[occurrence]);
// Thread-safe cache write
{
std::lock_guard<std::shared_mutex> writeLock(cacheMutex);
hexSumCache[cacheKey] = ult::to_string(hexSum);
}
} else {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Offset not found.");
#endif
return "";
}
}
const std::streampos totalOffset = hexSum + std::stoll(offsetStr);
std::vector<char> hexBuffer(length);
std::vector<char> hexStream(length * 2);
#if !USING_FSTREAM_DIRECTIVE
FILE* file = fopen(filePath.c_str(), "rb");
if (!file) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Failed to open the file.");
#endif
return "";
}
if (fseek(file, totalOffset, SEEK_SET) != 0) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Error seeking to offset.");
#endif
fclose(file);
return "";
}
const size_t bytesRead = fread(hexBuffer.data(), sizeof(char), length, file);
if (bytesRead == length) {
static constexpr char hexDigits[] = "0123456789ABCDEF";
for (size_t i = 0; i < length; ++i) {
hexStream[i * 2] = hexDigits[(hexBuffer[i] >> 4) & 0xF];
hexStream[i * 2 + 1] = hexDigits[hexBuffer[i] & 0xF];
}
} else {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Error reading data from file or end of file reached.");
#endif
fclose(file);
return "";
}
fclose(file);
#else
std::ifstream file(filePath, std::ios::binary);
if (!file) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Failed to open the file.");
#endif
return "";
}
file.seekg(totalOffset);
if (!file) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Error seeking to offset.");
#endif
return "";
}
file.read(hexBuffer.data(), length);
if (file.gcount() == static_cast<std::streamsize>(length)) {
static constexpr char hexDigits[] = "0123456789ABCDEF";
for (size_t i = 0; i < length; ++i) {
hexStream[i * 2] = hexDigits[(hexBuffer[i] >> 4) & 0xF];
hexStream[i * 2 + 1] = hexDigits[hexBuffer[i] & 0xF];
}
} else {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Error reading data from file or end of file reached.");
#endif
return "";
}
file.close();
#endif
std::string result(hexStream.begin(), hexStream.end());
result = stringToUppercase(result);
return result;
}
/**
* @brief Finds and replaces hexadecimal data in a file.
*
* This function searches for occurrences of hexadecimal data in a binary file
* and replaces them with a specified hexadecimal replacement data.
*
* @param filePath The path to the binary file.
* @param hexDataToReplace The hexadecimal data to search for and replace.
* @param hexDataReplacement The hexadecimal data to replace with.
* @param occurrence The occurrence/index of the data to replace (default is "0" to replace all occurrences).
*/
std::string replaceHexPlaceholder(const std::string& arg, const std::string& hexPath) {
std::string replacement = arg;
const std::string searchString = "{hex_file(";
const size_t startPos = replacement.find(searchString);
const size_t endPos = replacement.find(")}");
if (startPos != std::string::npos && endPos != std::string::npos && endPos > startPos) {
const std::string placeholderContent = replacement.substr(startPos + searchString.length(), endPos - startPos - searchString.length());
// Split the placeholder content into its components (customAsciiPattern, offsetStr, length)
std::vector<std::string> components;
// Use StringStream instead of std::istringstream
StringStream componentStream(placeholderContent);
std::string component;
while (componentStream.getline(component, ',')) {
trim(component);
components.push_back(component);
}
if (components.size() == 3) {
// Extract individual components
const std::string customAsciiPattern = components[0];
const std::string offsetStr = components[1];
const size_t length = std::stoul(components[2]);
// Call the parsing function and replace the placeholder
const std::string parsedResult = parseHexDataAtCustomOffset(hexPath, customAsciiPattern, offsetStr, length);
// Only replace if parsedResult returns a non-empty string
if (!parsedResult.empty()) {
// Replace the entire placeholder with the parsed result
replacement.replace(startPos, endPos - startPos + searchString.length() + 2, parsedResult);
}
}
}
return replacement;
}
/**
* @brief Extracts the version string from a binary file.
*
* This function reads a binary file and searches for a version pattern
* in the format "v#.#.#" (e.g., "v1.2.3").
*
* @param filePath The path to the binary file.
* @return The version string if found; otherwise, an empty string.
*/
std::string extractVersionFromBinary(const std::string &filePath) {
#if !USING_FSTREAM_DIRECTIVE
// Step 1: Open the binary file
FILE* file = fopen(filePath.c_str(), "rb");
if (!file) {
return ""; // Return empty string if file cannot be opened
}
// Get the file size
fseek(file, 0, SEEK_END);
const std::streamsize size = ftell(file);
fseek(file, 0, SEEK_SET);
// Read the entire file into a buffer
std::vector<uint8_t> buffer(size);
const size_t bytesRead = fread(buffer.data(), sizeof(uint8_t), size, file);
fclose(file); // Close the file after reading
if (bytesRead != static_cast<size_t>(size)) {
return ""; // Return empty string if reading fails
}
#else
// Step 1: Read the entire binary file into a vector
std::ifstream file(filePath, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
return ""; // Return empty string if file cannot be opened
}
const std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8_t> buffer(size);
if (!file.read(reinterpret_cast<char*>(buffer.data()), size)) {
return ""; // Return empty string if reading fails
}
#endif
// Step 2: Search for the pattern "v#.#.#"
const char* data = reinterpret_cast<const char*>(buffer.data());
for (std::streamsize i = 0; i < size; ++i) {
if (data[i] == 'v' && i + 5 < size &&
std::isdigit(data[i + 1]) && data[i + 2] == '.' &&
std::isdigit(data[i + 3]) && data[i + 4] == '.' &&
std::isdigit(data[i + 5])) {
// Extract the version string
return std::string(data + i, 6); // Return the version string
}
}
return ""; // Return empty string if no match is found
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,400 @@
/********************************************************************************
* File: json_funcs.cpp
* Author: ppkantorski
* Description:
* This source file provides functions for working with JSON files in C++ using
* the `cJSON` library. It includes a function to read JSON data from a file.
*
* For the latest updates and contributions, visit the project's GitHub repository.
* (GitHub Repository: https://github.com/ppkantorski/Ultrahand-Overlay)
*
* Note: Please be aware that this notice cannot be altered or removed. It is a part
* of the project's documentation and must remain intact.
*
* Licensed under both GPLv2 and CC-BY-4.0
* Copyright (c) 2024 ppkantorski
********************************************************************************/
#include "json_funcs.hpp"
namespace ult {
static std::mutex json_access_mutex;
/**
* @brief Reads JSON data from a file and returns it as a `json_t` object.
*
* @param filePath The path to the JSON file.
* @return A `json_t` object representing the parsed JSON data. Returns `nullptr` on error.
*/
json_t* readJsonFromFile(const std::string& filePath) {
std::lock_guard<std::mutex> lock(json_access_mutex);
#if !USING_FSTREAM_DIRECTIVE
FILE* file = fopen(filePath.c_str(), "rb");
if (!file) {
return nullptr;
}
// Get file size
fseek(file, 0, SEEK_END);
const long fileSize = ftell(file);
fseek(file, 0, SEEK_SET);
// Check for reasonable file size
if (fileSize <= 0 || fileSize > 6 * 1024 * 1024) { // 6MB limit
fclose(file);
return nullptr;
}
// Use vector<char> for better performance and explicit null termination
std::vector<char> buffer;
buffer.resize(static_cast<size_t>(fileSize) + 1); // +1 for null terminator
// Read the file in one operation
const size_t bytesRead = fread(buffer.data(), 1, static_cast<size_t>(fileSize), file);
fclose(file);
if (bytesRead != static_cast<size_t>(fileSize)) {
return nullptr;
}
// Ensure null termination for cJSON
buffer[bytesRead] = '\0';
#else
std::ifstream file(filePath, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
return nullptr;
}
// Get file size from current position (end)
const std::streampos fileSize = file.tellg();
file.seekg(0, std::ios::beg);
// Check for reasonable file size
if (fileSize <= 0 || fileSize > 6 * 1024 * 1024) {
return nullptr;
}
// Use vector<char> for better performance and explicit null termination
std::vector<char> buffer;
buffer.resize(static_cast<size_t>(fileSize) + 1); // +1 for null terminator
file.read(buffer.data(), static_cast<std::streamsize>(fileSize));
// Check how much was actually read
std::streamsize actualRead = file.gcount();
if (actualRead != fileSize) {
return nullptr;
}
// Ensure null termination for cJSON
buffer[actualRead] = '\0';
file.close();
#endif
// Parse the JSON content - pass buffer directly to avoid string copy
cJSON* root = cJSON_Parse(buffer.data());
if (!root) {
#if USING_LOGGING_DIRECTIVE
const char* error_ptr = cJSON_GetErrorPtr();
if (error_ptr) {
if (!disableLogging)
logMessage("JSON parsing error: " + std::string(error_ptr));
}
#endif
return nullptr;
}
return reinterpret_cast<json_t*>(root);
}
/**
* @brief Parses a JSON string into a json_t object.
*
* This function takes a JSON string as input and parses it into a json_t object using cJSON library's `cJSON_Parse` function.
* If parsing fails, it logs the error and returns nullptr.
*
* @param input The input JSON string to parse.
* @return A json_t object representing the parsed JSON, or nullptr if parsing fails.
*/
json_t* stringToJson(const std::string& input) {
cJSON* jsonObj = cJSON_Parse(input.c_str());
if (!jsonObj) {
#if USING_LOGGING_DIRECTIVE
const char* error_ptr = cJSON_GetErrorPtr();
if (error_ptr != nullptr) {
if (!disableLogging)
logMessage("Failed to parse JSON: " + std::string(error_ptr));
}
#endif
return nullptr; // Return nullptr to indicate failure clearly
}
return reinterpret_cast<json_t*>(jsonObj);
}
// Function to get a string from a JSON object
std::string getStringFromJson(const json_t* root, const char* key) {
const cJSON* croot = reinterpret_cast<const cJSON*>(root);
const cJSON* value = cJSON_GetObjectItemCaseSensitive(croot, key);
if (value && cJSON_IsString(value) && value->valuestring) {
return value->valuestring;
} else {
return ""; // Key not found or not a string, return empty string/char*
}
}
/**
* @brief Loads a JSON file from the specified path and retrieves a string value for a given key.
*
* This function combines the functionality of loading a JSON file and retrieving a string value associated
* with a given key in a single step.
*
* @param filePath The path to the JSON file.
* @param key The key whose associated string value is to be retrieved.
* @return A string containing the value associated with the given key, or an empty string if the key is not found.
*/
std::string getStringFromJsonFile(const std::string& filePath, const std::string& key) {
// Load JSON from file using a smart pointer
std::unique_ptr<json_t, JsonDeleter> root(readJsonFromFile(filePath), JsonDeleter());
if (!root) {
#if USING_LOGGING_DIRECTIVE
logMessage("Failed to load JSON file from path: " + filePath);
#endif
return "";
}
// Retrieve the string value associated with the key
cJSON* croot = reinterpret_cast<cJSON*>(root.get());
cJSON* jsonKey = cJSON_GetObjectItemCaseSensitive(croot, key.c_str());
const char* value = (cJSON_IsString(jsonKey) && jsonKey->valuestring) ? jsonKey->valuestring : nullptr;
// Check if the value was found and return it
if (value) {
return std::string(value);
} else {
#if USING_LOGGING_DIRECTIVE
logMessage("Key not found or not a string in JSON: " + key);
#endif
return "";
}
}
/**
* @brief Sets a value in a JSON file, creating the file if it doesn't exist.
*
* @param filePath The path to the JSON file.
* @param key The key to set.
* @param value The value to set (auto-detected type).
* @param createIfNotExists Whether to create the file if it doesn't exist.
* @return true if successful, false otherwise.
*/
bool setJsonValue(const std::string& filePath, const std::string& key, const std::string& value, bool createIfNotExists) {
// Try to load existing file
std::unique_ptr<json_t, JsonDeleter> root(readJsonFromFile(filePath), JsonDeleter());
cJSON* croot = nullptr;
// If file doesn't exist, create new JSON object if allowed
if (!root) {
if (!createIfNotExists) {
return false;
}
croot = cJSON_CreateObject();
if (!croot) {
return false;
}
root.reset(reinterpret_cast<json_t*>(croot));
} else {
croot = reinterpret_cast<cJSON*>(root.get());
}
// FIXED: Better value type detection
cJSON* jsonValue = nullptr;
// Trim whitespace first
std::string trimmedValue = value;
// Remove leading whitespace
trimmedValue.erase(0, trimmedValue.find_first_not_of(" \t\n\r"));
// Remove trailing whitespace
trimmedValue.erase(trimmedValue.find_last_not_of(" \t\n\r") + 1);
if (trimmedValue.empty()) {
jsonValue = cJSON_CreateString("");
} else if (trimmedValue == "true") {
jsonValue = cJSON_CreateBool(1);
} else if (trimmedValue == "false") {
jsonValue = cJSON_CreateBool(0);
} else if (trimmedValue == "null") {
jsonValue = cJSON_CreateNull();
} else {
// Try parsing as number (integer or float)
char* endPtr = nullptr;
errno = 0;
// Try as integer first
const long longValue = std::strtol(trimmedValue.c_str(), &endPtr, 10);
if (endPtr == trimmedValue.c_str() + trimmedValue.length() && errno == 0) {
// Successfully parsed as integer
jsonValue = cJSON_CreateNumber(static_cast<double>(longValue));
} else {
// Try as float
endPtr = nullptr;
errno = 0;
const double doubleValue = std::strtod(trimmedValue.c_str(), &endPtr);
if (endPtr == trimmedValue.c_str() + trimmedValue.length() && errno == 0) {
// Successfully parsed as float
jsonValue = cJSON_CreateNumber(doubleValue);
} else {
// Treat as string
jsonValue = cJSON_CreateString(trimmedValue.c_str());
}
}
}
if (!jsonValue) {
return false;
}
// Delete existing item if it exists
cJSON_DeleteItemFromObject(croot, key.c_str());
// Add the new value
cJSON_AddItemToObject(croot, key.c_str(), jsonValue);
// Save to file with formatted output for better readability
char* jsonString = cJSON_Print(croot); // Use formatted output instead of PrintUnformatted
if (!jsonString) {
return false;
}
bool success = false;
{
std::lock_guard<std::mutex> lock(json_access_mutex);
#if !USING_FSTREAM_DIRECTIVE
FILE* file = fopen(filePath.c_str(), "w"); // Use text mode for JSON
if (file) {
const size_t jsonLength = std::strlen(jsonString);
const size_t bytesWritten = fwrite(jsonString, 1, jsonLength, file);
success = (bytesWritten == jsonLength);
fclose(file);
}
#else
std::ofstream file(filePath); // Use text mode for JSON
if (file.is_open()) {
file << jsonString;
success = !file.fail();
file.close();
}
#endif
}
cJSON_free(jsonString);
return success;
}
/**
* @brief Renames a key in a JSON file.
*
* @param filePath The path to the JSON file.
* @param oldKey The current key name.
* @param newKey The new key name.
* @return true if successful, false otherwise.
*/
bool renameJsonKey(const std::string& filePath, const std::string& oldKey, const std::string& newKey) {
// Try to load existing file
std::unique_ptr<json_t, JsonDeleter> root(readJsonFromFile(filePath), JsonDeleter());
if (!root) {
return false;
}
cJSON* croot = reinterpret_cast<cJSON*>(root.get());
// Check if old key exists
cJSON* value = cJSON_GetObjectItemCaseSensitive(croot, oldKey.c_str());
if (!value) {
return false;
}
// Detach the value from the object
cJSON_DetachItemFromObject(croot, oldKey.c_str());
// Add it back with the new key
cJSON_AddItemToObject(croot, newKey.c_str(), value);
// Save to file
char* jsonString = cJSON_Print(croot); // Use formatted output
if (!jsonString) {
return false;
}
bool success = false;
{
std::lock_guard<std::mutex> lock(json_access_mutex);
#if !USING_FSTREAM_DIRECTIVE
FILE* file = fopen(filePath.c_str(), "w"); // Use text mode
if (file) {
const size_t jsonLength = std::strlen(jsonString);
const size_t bytesWritten = fwrite(jsonString, 1, jsonLength, file);
success = (bytesWritten == jsonLength);
fclose(file);
}
#else
std::ofstream file(filePath); // Use text mode
if (file.is_open()) {
file << jsonString;
success = !file.fail();
file.close();
}
#endif
}
cJSON_free(jsonString);
return success;
}
void pushNotificationJson(const std::string& text, size_t fontSize) {
u64 tick = armGetSystemTick();
std::string filename = "20-" + std::to_string(tick) + ".notify"; // priority 20 default
// Build full path
std::string fullPath = NOTIFICATIONS_PATH + filename;
// Create JSON object
cJSON* notif = cJSON_CreateObject();
if (!notif) return;
cJSON_AddStringToObject(notif, "text", text.c_str());
cJSON_AddNumberToObject(notif, "font_size", static_cast<double>(fontSize));
//cJSON_AddNumberToObject(notif, "arrival_ns", static_cast<double>(armTicksToNs(tick)));
// Serialize JSON
char* rendered = cJSON_PrintUnformatted(notif);
if (!rendered) {
cJSON_Delete(notif);
return;
}
// Write to file (C-style)
FILE* file = fopen(fullPath.c_str(), "wb");
if (file) {
fwrite(rendered, 1, strlen(rendered), file);
fclose(file);
}
cJSON_free(rendered);
cJSON_Delete(notif);
}
}

View File

@@ -0,0 +1,467 @@
/********************************************************************************
* File: list_funcs.cpp
* Author: ppkantorski
* Description:
* This source file contains function declarations and utility functions related
* to working with lists and vectors of strings. These functions are used in the
* Ultrahand Overlay project to perform various operations on lists, such as
* removing entries, filtering, and more.
*
* For the latest updates and contributions, visit the project's GitHub repository.
* (GitHub Repository: https://github.com/ppkantorski/Ultrahand-Overlay)
*
* Note: Please be aware that this notice cannot be altered or removed. It is a part
* of the project's documentation and must remain intact.
*
* Licensed under both GPLv2 and CC-BY-4.0
* Copyright (c) 2024 ppkantorski
********************************************************************************/
#include <list_funcs.hpp>
#include <mutex>
namespace ult {
// Thread-safe file access mutex
static std::mutex file_access_mutex;
std::vector<std::string> splitIniList(const std::string& value) {
std::vector<std::string> result;
std::string trimmed = value;
trim(trimmed);
if (trimmed.size() > 2 && trimmed.front() == '(' && trimmed.back() == ')') {
trimmed = trimmed.substr(1, trimmed.size() - 2);
ult::StringStream ss(trimmed);
std::string token;
while (ss.getline(token, ',')) {
trim(token);
result.push_back(token);
}
}
return result;
}
std::string joinIniList(const std::vector<std::string>& list) {
std::string result = "";
for (size_t i = 0; i < list.size(); ++i) {
result += list[i];
if (i + 1 < list.size()) {
result += ", ";
}
}
return result;
}
/**
* @brief Removes entries from a vector of strings that match a specified entry.
*
* This function removes entries from the `itemsList` vector of strings that match the `entry`.
*
* @param entry The entry to be compared against the elements in `itemsList`.
* @param itemsList The vector of strings from which matching entries will be removed.
*/
void removeEntryFromList(const std::string& entry, std::vector<std::string>& itemsList) {
itemsList.erase(std::remove_if(itemsList.begin(), itemsList.end(), [&](const std::string& item) {
return item.compare(0, entry.length(), entry) == 0;
}), itemsList.end());
}
/**
* @brief Filters a list of strings based on a specified filter list.
*
* This function filters a list of strings (`itemsList`) by removing entries that match any
* of the criteria specified in the `filterList`. It uses the `removeEntryFromList` function
* to perform the removal.
*
* @param filterList The list of entries to filter by. Entries in `itemsList` matching any entry in this list will be removed.
* @param itemsList The list of strings to be filtered.
*/
void filterItemsList(const std::vector<std::string>& filterList, std::vector<std::string>& itemsList) {
for (const auto& entry : filterList) {
removeEntryFromList(entry, itemsList);
}
}
// Function to read file into a vector of strings with optional cap
std::vector<std::string> readListFromFile(const std::string& filePath, size_t maxLines) {
std::lock_guard<std::mutex> lock(file_access_mutex);
std::vector<std::string> lines;
#if !USING_FSTREAM_DIRECTIVE
FILE* file = fopen(filePath.c_str(), "r");
if (!file) {
#if USING_LOGGING_DIRECTIVE
logMessage("Unable to open file: " + filePath);
#endif
return lines;
}
static constexpr size_t BUFFER_SIZE = 8192;
char buffer[BUFFER_SIZE];
size_t len;
while (fgets(buffer, BUFFER_SIZE, file)) {
// Check cap before processing
if (maxLines > 0 && lines.size() >= maxLines) {
break;
}
// More efficient newline removal
len = strlen(buffer);
if (len > 0 && buffer[len - 1] == '\n') {
buffer[len - 1] = '\0';
--len;
// Also remove carriage return if present
if (len > 0 && buffer[len - 1] == '\r') {
buffer[len - 1] = '\0';
}
}
lines.emplace_back(buffer);
}
fclose(file);
#else
std::ifstream file(filePath);
if (!file.is_open()) {
#if USING_LOGGING_DIRECTIVE
logMessage("Unable to open file: " + filePath);
#endif
return lines;
}
std::string line;
while (std::getline(file, line)) {
// Check cap before adding
if (maxLines > 0 && lines.size() >= maxLines) {
break;
}
// Remove carriage return if present (getline removes \n but not \r)
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
lines.emplace_back(std::move(line));
}
file.close();
#endif
return lines;
}
// Function to get an entry from the list based on the index
std::string getEntryFromListFile(const std::string& listPath, size_t listIndex) {
std::lock_guard<std::mutex> lock(file_access_mutex);
#if !USING_FSTREAM_DIRECTIVE
FILE* file = fopen(listPath.c_str(), "r");
if (!file) {
#if USING_LOGGING_DIRECTIVE
logMessage("Unable to open file: " + listPath);
#endif
return "";
}
static constexpr size_t BUFFER_SIZE = 8192;
char buffer[BUFFER_SIZE];
// Skip lines until reaching the desired index
for (size_t i = 0; i < listIndex; ++i) {
if (!fgets(buffer, BUFFER_SIZE, file)) {
fclose(file);
return ""; // Index out of bounds
}
}
// Read the target line
if (!fgets(buffer, BUFFER_SIZE, file)) {
fclose(file);
return ""; // Index out of bounds
}
fclose(file);
// Efficiently remove newline character
const size_t len = strlen(buffer);
if (len > 0 && buffer[len - 1] == '\n') {
buffer[len - 1] = '\0';
if (len > 1 && buffer[len - 2] == '\r') {
buffer[len - 2] = '\0';
}
}
return std::string(buffer);
#else
std::ifstream file(listPath);
if (!file.is_open()) {
#if USING_LOGGING_DIRECTIVE
logMessage("Unable to open file: " + listPath);
#endif
return "";
}
std::string line;
// Skip lines until reaching the desired index
for (size_t i = 0; i < listIndex; ++i) {
if (!std::getline(file, line)) {
return ""; // Index out of bounds
}
}
// Read the target line
if (!std::getline(file, line)) {
return ""; // Index out of bounds
}
file.close();
return line;
#endif
}
/**
* @brief Splits a string into a vector of strings using a delimiter.
*
* This function splits the input string into multiple strings using the specified delimiter.
*
* @param str The input string to split.
* @return A vector of strings containing the split values.
*/
std::vector<std::string> stringToList(const std::string& str) {
std::vector<std::string> result;
if (str.empty()) {
return result;
}
// Check if the input string starts and ends with '(' and ')' or '[' and ']'
if ((str.front() == '(' && str.back() == ')') || (str.front() == '[' && str.back() == ']')) {
// Work directly with the original string using indices instead of creating substring
size_t start = 1; // Skip opening bracket/paren
size_t end = 1;
const size_t values_end = str.size() - 1; // Skip closing bracket/paren
// Pre-declare item string to avoid repeated allocations
std::string item;
// Iterate through the string manually to split by commas
while ((end = str.find(',', start)) != std::string::npos && end < values_end) {
// Extract item directly from original string without creating substring
item.assign(str, start, end - start);
// Trim leading and trailing spaces
trim(item);
// Remove quotes from each token if necessary
removeQuotes(item);
result.push_back(std::move(item));
start = end + 1;
}
// Handle the last item after the last comma
if (start < values_end) {
item.assign(str, start, values_end - start);
trim(item);
removeQuotes(item);
result.push_back(std::move(item));
}
}
return result;
}
// Function to read file into a set of strings
std::unordered_set<std::string> readSetFromFile(const std::string& filePath) {
std::lock_guard<std::mutex> lock(file_access_mutex);
std::unordered_set<std::string> lines;
#if !USING_FSTREAM_DIRECTIVE
FILE* file = fopen(filePath.c_str(), "r");
if (!file) {
#if USING_LOGGING_DIRECTIVE
logMessage("Unable to open file: " + filePath);
#endif
return lines;
}
static constexpr size_t BUFFER_SIZE = 8192;
char buffer[BUFFER_SIZE];
size_t len;
while (fgets(buffer, BUFFER_SIZE, file)) {
// Remove trailing newline character if it exists
len = strlen(buffer);
if (len > 0 && buffer[len - 1] == '\n') {
buffer[len - 1] = '\0';
}
lines.insert(buffer);
}
fclose(file);
#else
std::ifstream file(filePath);
if (!file.is_open()) {
#if USING_LOGGING_DIRECTIVE
logMessage("Unable to open file: " + filePath);
#endif
return lines;
}
std::string line;
while (std::getline(file, line)) {
lines.insert(std::move(line));
}
file.close();
#endif
return lines;
}
// Function to write a set to a file
void writeSetToFile(const std::unordered_set<std::string>& fileSet, const std::string& filePath) {
std::lock_guard<std::mutex> lock(file_access_mutex);
#if !USING_FSTREAM_DIRECTIVE
FILE* file = fopen(filePath.c_str(), "w");
if (!file) {
#if USING_LOGGING_DIRECTIVE
logMessage("Failed to open file: " + filePath);
#endif
return;
}
for (const auto& entry : fileSet) {
fprintf(file, "%s\n", entry.c_str());
}
fclose(file);
#else
std::ofstream file(filePath);
if (!file.is_open()) {
#if USING_LOGGING_DIRECTIVE
logMessage("Failed to open file: " + filePath);
#endif
return;
}
for (const auto& entry : fileSet) {
file << entry << '\n';
}
file.close();
#endif
}
// Function to compare two file lists and save duplicates to an output file
void compareFilesLists(const std::string& txtFilePath1, const std::string& txtFilePath2, const std::string& outputTxtFilePath) {
// Read files into sets
std::unordered_set<std::string> fileSet1 = readSetFromFile(txtFilePath1);
std::unordered_set<std::string> fileSet2 = readSetFromFile(txtFilePath2);
std::unordered_set<std::string> duplicateFiles;
// Find intersection (common elements) between the two sets
for (const auto& entry : fileSet1) {
if (fileSet2.count(entry)) {
duplicateFiles.insert(entry);
}
}
// Write the duplicates to the output file
writeSetToFile(duplicateFiles, outputTxtFilePath);
}
// Helper function to read a text file and process each line with a callback
void processFileLines(const std::string& filePath, const std::function<void(const std::string&)>& callback) {
std::lock_guard<std::mutex> lock(file_access_mutex);
#if !USING_FSTREAM_DIRECTIVE
FILE* file = fopen(filePath.c_str(), "r");
if (!file) {
#if USING_LOGGING_DIRECTIVE
logMessage("Unable to open file: " + filePath);
#endif
return;
}
// OPTIMIZATION 1: Larger buffer for better I/O performance
static constexpr size_t BUFFER_SIZE = 8192;
char buffer[BUFFER_SIZE];
while (fgets(buffer, BUFFER_SIZE, file)) {
// OPTIMIZATION 2: Find newline directly instead of strlen()
char* newlinePos = strchr(buffer, '\n');
if (newlinePos) {
*newlinePos = '\0'; // Remove newline in-place
}
// OPTIMIZATION 3: Pass buffer directly - no string construction overhead
callback(std::string(buffer));
}
fclose(file);
#else
// OPTIMIZATION 4: Use faster I/O for fstream version
std::ifstream file(filePath);
if (!file.is_open()) {
#if USING_LOGGING_DIRECTIVE
logMessage("Unable to open file: " + filePath);
#endif
return;
}
// OPTIMIZATION 5: Reserve string capacity to avoid reallocations
std::string line;
line.reserve(256); // Reasonable default for most lines
while (std::getline(file, line)) {
callback(line);
}
#endif
}
void compareWildcardFilesLists(
const std::string& wildcardPatternFilePath,
const std::string& txtFilePath,
const std::string& outputTxtFilePath
) {
// STEP 1: Read target file into fast lookup set (only once)
const std::unordered_set<std::string> targetLines = readSetFromFile(txtFilePath);
std::unordered_set<std::string> duplicates;
// STEP 2: Get wildcard files
std::vector<std::string> wildcardFiles = getFilesListByWildcards(wildcardPatternFilePath);
// STEP 3: Process each wildcard file line-by-line (minimum memory)
for (auto& filePath : wildcardFiles) {
if (filePath == txtFilePath) {
filePath = "";
continue;
}
// Process line-by-line without loading entire file into memory
processFileLines(filePath, [&](const std::string& line) {
// O(1) lookup + O(1) insert if duplicate found
if (targetLines.count(line)) {
duplicates.insert(line);
}
});
filePath = "";
}
// STEP 4: Write results
writeSetToFile(duplicates, outputTxtFilePath);
}
}

View File

@@ -0,0 +1,736 @@
/********************************************************************************
* File: mod_funcs.cpp
* Author: ppkantorski
* Description:
* This source file provides the implementations of functions declared in
* mod_funcs.hpp. These functions handle the conversion of `.pchtxt` mod files
* into `.ips` binary patches used by the Ultrahand Overlay project. This includes
* parsing, validating, and encoding patch data into the IPS format.
*
* For the latest updates and contributions, visit the project's GitHub repository.
* (GitHub Repository: https://github.com/ppkantorski/Ultrahand-Overlay)
*
* Note: Please be aware that this notice cannot be altered or removed. It is a part
* of the project's documentation and must remain intact.
*
* Licensed under both GPLv2 and CC-BY-4.0
* Copyright (c) 2024 ppkantorski
********************************************************************************/
#include <mod_funcs.hpp>
namespace ult {
//const std::string CHEAT_HEADER = "// auto generated by pchtxt2cheat\n\n";
const std::string CHEAT_TYPE = "04000000";
const std::string CHEAT_EXT = ".txt";
const std::string CHEAT_ENCODING = "ascii";
/**
* @brief Checks if a cheat already exists in the cheat file.
* @param cheatFilePath The path to the cheat file.
* @param newCheat The new cheat to check.
* @return True if the cheat exists, otherwise false.
*/
bool cheatExists(const std::string& cheatFilePath, const std::string& newCheat) {
#if !USING_FSTREAM_DIRECTIVE
FILE* cheatFile = fopen(cheatFilePath.c_str(), "r"); // Open the cheat file in read mode
if (!cheatFile) {
return false; // Return false if the file cannot be opened
}
//size_t len;
char buffer[1024]; // Buffer to store each line
while (fgets(buffer, sizeof(buffer), cheatFile)) {
// Remove newline character, if present
const size_t len = strlen(buffer);
if (len > 0 && buffer[len - 1] == '\n') {
buffer[len - 1] = '\0';
}
if (newCheat == buffer) {
fclose(cheatFile); // Close the file before returning
return true; // Cheat exists
}
}
fclose(cheatFile); // Close the file after processing
return false; // Cheat does not exist
#else
std::ifstream cheatFile(cheatFilePath);
if (!cheatFile) {
return false; // Return false if the file cannot be opened
}
std::string line;
while (std::getline(cheatFile, line)) {
if (line == newCheat) {
return true; // Cheat exists
}
}
return false; // Cheat does not exist
#endif
}
/**
* @brief Appends a new cheat to the cheat file.
* @param cheatFilePath The path to the cheat file.
* @param newCheat The new cheat to append.
*/
void appendCheatToFile(const std::string& cheatFilePath, const std::string& newCheat) {
#if !USING_FSTREAM_DIRECTIVE
FILE* cheatFile = fopen(cheatFilePath.c_str(), "a"); // Open the cheat file in append mode
if (!cheatFile) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Failed to open cheat file for appending: " + cheatFilePath);
#endif
return; // Handle the error accordingly
}
fprintf(cheatFile, "%s\n", newCheat.c_str()); // Write the new cheat followed by a newline
fclose(cheatFile); // Close the file
#else
std::ofstream cheatFile(cheatFilePath, std::ios::app);
if (!cheatFile) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Failed to open cheat file for appending: " + cheatFilePath);
#endif
return; // Handle the error accordingly
}
cheatFile << newCheat << std::endl; // Write the new cheat
#endif
}
/**
* @brief Extracts the cheat name from the given file path.
* @param filePath The full file path.
* @return The extracted cheat name.
*/
std::string extractCheatName(const std::string &filePath) {
const size_t lastSlash = filePath.find_last_of("/\\");
if (lastSlash == std::string::npos) {
return "";
}
const size_t secondLastSlash = filePath.find_last_of("/\\", lastSlash - 1);
if (secondLastSlash == std::string::npos) {
return "";
}
const std::string lastDir = filePath.substr(secondLastSlash + 1, lastSlash - secondLastSlash - 1);
std::string fileName = filePath.substr(lastSlash + 1);
const size_t dotPos = fileName.find_last_of('.');
if (dotPos != std::string::npos) {
fileName = fileName.substr(0, dotPos);
}
// If lastDir contains " - ", extract the part after " - "
const size_t dashPos = lastDir.find(" - ");
std::string cheatName = lastDir;
if (dashPos != std::string::npos) {
cheatName = lastDir.substr(dashPos + 3);
}
return cheatName + " " + fileName;
}
// Helper function to determine if a string is a valid title ID
bool isValidTitleID(const std::string &str) {
if (str.length() != 16) return false;
for (char c : str) {
if (!std::isxdigit(c)) return false; // Check if each character is a hexadecimal digit
}
return true;
}
// Function to find the title ID in the text, avoiding the @nsobid- line
std::string findTitleID(const std::string &text) {
const size_t nsobidPos = text.find("@nsobid-");
const size_t startPos = (nsobidPos != std::string::npos) ? nsobidPos + 40 + 8 : 0; // Skip past @nsobid- and its value
std::string potentialID;
for (size_t i = startPos; i <= text.length() - 16; ++i) {
potentialID = text.substr(i, 16);
if (isValidTitleID(potentialID)) {
return potentialID;
}
}
return "";
}
/**
* @brief Converts a .pchtxt file to a cheat file.
* @param pchtxtPath The file path to the .pchtxt file.
* @param cheatName The name of the cheat.
* @param outCheatPath The file path for the output cheat file.
* @return True if the conversion was successful, false otherwise.
*/
bool pchtxt2cheat(const std::string &pchtxtPath, std::string cheatName, std::string outCheatPath) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Starting pchtxt2cheat with pchtxtPath: " + pchtxtPath);
#endif
#if !USING_FSTREAM_DIRECTIVE
FILE* pchtxtFile = fopen(pchtxtPath.c_str(), "r");
if (!pchtxtFile) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Error: Unable to open file " + pchtxtPath);
#endif
return false;
}
// Read the entire file into a string
std::string pchtxt;
char buffer[4096];
while (fgets(buffer, sizeof(buffer), pchtxtFile)) {
pchtxt += buffer;
}
fclose(pchtxtFile);
#else
std::ifstream pchtxtFile(pchtxtPath);
if (!pchtxtFile) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Error: Unable to open file " + pchtxtPath);
#endif
return false;
}
std::string pchtxt((std::istreambuf_iterator<char>(pchtxtFile)), std::istreambuf_iterator<char>());
#endif
const size_t nsobidPos = pchtxt.find("@nsobid-");
if (nsobidPos == std::string::npos) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Error: Could not find bid in pchtxt file, the file is likely invalid.");
#endif
return false;
}
const std::string bid = pchtxt.substr(nsobidPos + 8, 40);
const std::string bidShort = bid.substr(0, 16);
const std::string tid = findTitleID(pchtxt);
if (tid.empty()) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Error: Could not find TID in pchtxt file, the file is likely invalid.");
#endif
return false;
}
std::string cheatFilePath;
if (outCheatPath.empty()) {
const std::string folderPath = "sdmc:/atmosphere/contents/" + tid + "/cheats/";
createDirectory(folderPath);
cheatFilePath = folderPath + bidShort + CHEAT_EXT;
} else {
cheatFilePath = outCheatPath;
}
#if !USING_FSTREAM_DIRECTIVE
FILE* existingCheatFile = fopen(cheatFilePath.c_str(), "r");
bool cheatNameExists = false;
if (existingCheatFile) {
char line[256];
while (fgets(line, sizeof(line), existingCheatFile)) {
if (std::string(line) == "[" + cheatName + "]\n") {
cheatNameExists = true;
break;
}
}
fclose(existingCheatFile);
}
#else
std::ifstream existingCheatFile(cheatFilePath);
bool cheatNameExists = false;
if (existingCheatFile) {
std::string line;
while (std::getline(existingCheatFile, line)) {
if (line == "[" + cheatName + "]") {
cheatNameExists = true;
break;
}
}
}
existingCheatFile.close();
#endif
// Open output cheat file
#if !USING_FSTREAM_DIRECTIVE
FILE* outCheatFile = fopen(cheatFilePath.c_str(), "a");
if (!outCheatFile) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Error: Unable to create cheat file " + cheatFilePath);
#endif
return false;
}
#else
std::ofstream outCheatFile(cheatFilePath, std::ios::app);
if (!outCheatFile) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Error: Unable to create cheat file " + cheatFilePath);
#endif
return false;
}
#endif
if (!cheatNameExists) {
#if !USING_FSTREAM_DIRECTIVE
fprintf(outCheatFile, "[%s]\n", cheatName.c_str());
#else
outCheatFile << "[" << cheatName << "]\n";
#endif
}
int offset = 0;
bool enabled = true;
int validCheatsProcessed = 0; // ADDED: Track number of valid cheats processed
StringStream iss(pchtxt); // Use your custom StringStream
std::string line;
std::string addrStr, valStr;
size_t spacePos;
int codeOffset;
std::string cheatLine;
char offsetBuffer[9];
// Use your custom getline method instead of std::getline
while (iss.getline(line, '\n')) { // Custom getline with newline as the delimiter
// strip inline C++-style comments
const auto slashPos = line.find("//");
if (slashPos != std::string::npos)
line = line.substr(0, slashPos);
// strip inline hash comments (but leave full-line # for headers)
const auto hashPos = line.find('#');
if (hashPos != std::string::npos && hashPos > 0)
line = line.substr(0, hashPos);
trim(line);
if (line.empty() || line[0] == '#') continue;
if (line.find("@flag offset_shift ") == 0) {
const std::string offsetStr = line.substr(19);
offset = (offsetStr.find("0x") == 0 ? std::strtol(offsetStr.c_str(), nullptr, 16) : std::strtol(offsetStr.c_str(), nullptr, 10)) - 0x100;
continue;
}
if (line.find("@enabled") == 0) {
enabled = true;
continue;
}
if (line.find("@disabled") == 0) {
enabled = false;
continue;
}
if (line.find("@stop") == 0) {
break;
}
if (!enabled) {
continue;
}
spacePos = line.find(' ');
if (spacePos == std::string::npos) {
continue;
}
addrStr = line.substr(0, spacePos);
valStr = line.substr(spacePos + 1);
if (addrStr.find_first_not_of("0123456789abcdefABCDEF") != std::string::npos || valStr.find_first_not_of("0123456789abcdefABCDEF") != std::string::npos) {
continue;
}
codeOffset = std::strtol(valStr.c_str(), nullptr, 16) + offset;
snprintf(offsetBuffer, sizeof(offsetBuffer), "%08X", codeOffset);
cheatLine = CHEAT_TYPE + " " + addrStr + " " + hexToReversedHex(offsetBuffer);
#if !USING_FSTREAM_DIRECTIVE
// Check if cheat already exists
FILE* checkFile = fopen(cheatFilePath.c_str(), "r");
bool exists = false;
if (checkFile) {
char checkLine[256];
while (fgets(checkLine, sizeof(checkLine), checkFile)) {
if (std::string(checkLine) == cheatLine + "\n") {
exists = true;
break;
}
}
fclose(checkFile);
}
if (!exists) {
fprintf(outCheatFile, "%s\n", cheatLine.c_str());
validCheatsProcessed++; // ADDED: Increment counter for new cheats
}
#else
if (!cheatExists(cheatFilePath, cheatLine)) {
outCheatFile << cheatLine << "\n";
validCheatsProcessed++; // ADDED: Increment counter for new cheats
}
#endif
}
#if !USING_FSTREAM_DIRECTIVE
fclose(outCheatFile);
#else
outCheatFile.close();
#endif
// ADDED: Check if any valid cheats were processed
if (validCheatsProcessed == 0) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Warning: No valid patch data found to convert to cheats in " + pchtxtPath);
#endif
return false;
}
return true;
}
// Corrected helper function to convert values to big-endian format
uint32_t toBigEndian(uint32_t value) {
return ((value & 0x000000FF) << 24) |
((value & 0x0000FF00) << 8) |
((value & 0x00FF0000) >> 8) |
((value & 0xFF000000) >> 24);
}
uint16_t toBigEndian(uint16_t value) {
return ((value & 0x00FF) << 8) |
((value & 0xFF00) >> 8);
}
// Helper function to convert a vector of bytes to a hex string for logging
std::string hexToString(const std::vector<uint8_t>& bytes) {
StringStream oss; // Use your custom StringStream
oss.hex(); // Enable hex mode for the stream
for (uint8_t byte : bytes) {
if (byte < 0x10) {
oss << "0"; // Append leading zero for single-digit hex values
}
oss << static_cast<int>(byte); // Convert byte to int and then append it
}
return oss.str(); // Return the final hex string
}
/**
* @brief Converts a .pchtxt file to an IPS file using fstream.
*
* This function reads the contents of a .pchtxt file, extracts the address-value pairs,
* and generates an IPS file with the provided output folder.
*
* @param pchtxtPath The file path to the .pchtxt file.
* @param outputFolder The folder path for the output IPS file.
* @return True if the conversion was successful, false otherwise.
*/
bool pchtxt2ips(const std::string& pchtxtPath, const std::string& outputFolder) {
#if !USING_FSTREAM_DIRECTIVE
// Use FILE* for reading
FILE* pchtxtFile = fopen(pchtxtPath.c_str(), "r");
if (!pchtxtFile) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Error: Unable to open file " + pchtxtPath);
#endif
return false;
}
std::vector<std::pair<uint32_t, std::vector<uint8_t>>> patches;
char lineBuffer[512]; // Fixed: Use char buffer for fgets
std::string line;
uint32_t lineNum = 0;
std::string nsobid;
int offset = 0; // Default offset
bool enabled = true;
uint32_t address;
uint8_t byte;
std::vector<uint8_t> valueBytes;
std::string offsetStr;
while (fgets(lineBuffer, sizeof(lineBuffer), pchtxtFile) != nullptr) {
line = lineBuffer; // Convert to string
++lineNum;
if (line.empty() || line.front() == '@' || !enabled) {
if (line.find("@nsobid-") == 0) {
nsobid = line.substr(8);
}
if (line.find("@flag offset_shift ") == 0) {
offsetStr = line.substr(19);
offset = (offsetStr.find("0x") == 0 ? std::strtol(offsetStr.c_str(), nullptr, 16) : std::strtol(offsetStr.c_str(), nullptr, 10));
}
if (line.find("@enabled") == 0) {
enabled = true;
continue;
}
if (line.find("@disabled") == 0) {
enabled = false;
continue;
}
if (line.find("@stop") == 0) {
break;
}
continue; // Skip empty lines and lines starting with '@'
}
StringStream iss(line);
std::string addressStr, valueStr;
if (!(iss >> addressStr >> valueStr)) {
continue;
}
char* endPtr;
address = std::strtoul(addressStr.c_str(), &endPtr, 16) + offset; // Adjust address by offset
if (*endPtr != '\0') {
continue;
}
for (size_t i = 0; i < valueStr.length(); i += 2) {
byte = ult::stoi(valueStr.substr(i, 2), nullptr, 16);
valueBytes.push_back(byte);
}
if (valueBytes.empty()) {
continue;
}
patches.push_back(std::make_pair(address, valueBytes));
valueBytes.clear();
}
fclose(pchtxtFile);
#else
// Use fstream for reading
std::ifstream pchtxtFile(pchtxtPath);
if (!pchtxtFile) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Error: Unable to open file " + pchtxtPath);
#endif
return false;
}
std::vector<std::pair<uint32_t, std::vector<uint8_t>>> patches;
std::string line;
uint32_t lineNum = 0;
std::string nsobid;
int offset = 0; // Default offset
uint32_t address;
uint8_t byte;
std::vector<uint8_t> valueBytes;
std::string offsetStr;
while (std::getline(pchtxtFile, line)) {
++lineNum;
if (line.empty() || line.front() == '@') {
if (line.find("@nsobid-") == 0) {
nsobid = line.substr(8);
}
if (line.find("@flag offset_shift ") == 0) {
offsetStr = line.substr(19);
offset = (offsetStr.find("0x") == 0 ? std::strtol(offsetStr.c_str(), nullptr, 16) : std::strtol(offsetStr.c_str(), nullptr, 10));
}
if (line.find("@stop") == 0) {
break;
}
continue; // Skip empty lines and lines starting with '@'
}
StringStream iss(line);
std::string addressStr, valueStr;
if (!(iss >> addressStr >> valueStr)) {
continue;
}
char* endPtr;
address = std::strtoul(addressStr.c_str(), &endPtr, 16) + offset; // Adjust address by offset
if (*endPtr != '\0') {
continue;
}
for (size_t i = 0; i < valueStr.length(); i += 2) {
byte = ult::stoi(valueStr.substr(i, 2), nullptr, 16);
valueBytes.push_back(byte);
}
if (valueBytes.empty()) {
continue;
}
patches.push_back(std::make_pair(address, valueBytes));
valueBytes.clear();
}
pchtxtFile.close();
#endif
// CHECK: Return false if no patches were found
if (patches.empty()) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Warning: No valid patches found in " + pchtxtPath);
#endif
return false;
}
if (nsobid.empty()) {
nsobid = pchtxtPath.substr(pchtxtPath.find_last_of("/\\") + 1);
nsobid = nsobid.substr(0, nsobid.find_last_of("."));
}
// Trim any newline characters from nsobid
trim(nsobid);
trimNewline(nsobid);
const std::string ipsFileName = nsobid + ".ips";
const std::string ipsFilePath = outputFolder + ipsFileName;
#if !USING_FSTREAM_DIRECTIVE
// Use FILE* for writing
FILE* ipsFile = fopen(ipsFilePath.c_str(), "wb");
if (!ipsFile) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Error: Unable to create IPS file " + ipsFilePath);
#endif
return false;
}
fwrite(IPS32_HEAD_MAGIC, sizeof(char), std::strlen(IPS32_HEAD_MAGIC), ipsFile);
uint16_t valueLength;
uint32_t bigEndianAddress;
for (const auto& patch : patches) {
bigEndianAddress = toBigEndian(patch.first); // Convert address to big-endian
fwrite(&bigEndianAddress, sizeof(bigEndianAddress), 1, ipsFile); // Write address
valueLength = toBigEndian(static_cast<uint16_t>(patch.second.size())); // Convert length to big-endian
fwrite(&valueLength, sizeof(valueLength), 1, ipsFile); // Write length of value
fwrite(patch.second.data(), sizeof(uint8_t), patch.second.size(), ipsFile); // Write value
}
fwrite(IPS32_FOOT_MAGIC, sizeof(char), std::strlen(IPS32_FOOT_MAGIC), ipsFile);
fclose(ipsFile);
FILE* _pchtxtFile = fopen(pchtxtPath.c_str(), "r");
if (!_pchtxtFile) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Error: Unable to open file " + pchtxtPath);
#endif
return false; // Changed: Return false if we can't read the file for title ID
}
// Read the entire file into a string
std::string pchtxt;
char buffer[4096];
while (fgets(buffer, sizeof(buffer), _pchtxtFile)) {
pchtxt += buffer;
}
fclose(_pchtxtFile);
const std::string tid = findTitleID(pchtxt);
if (!tid.empty()) {
const std::string tidFilePath = outputFolder + tid;
FILE* tidFile = fopen(tidFilePath.c_str(), "w");
if (tidFile) {
fclose(tidFile); // Creates an empty file
}
} else {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Warning: Could not find Title ID in " + pchtxtPath);
#endif
}
#else
// Use fstream for writing
std::ofstream ipsFile(ipsFilePath, std::ios::binary);
if (!ipsFile) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Error: Unable to create IPS file " + ipsFilePath);
#endif
return false;
}
ipsFile.write(IPS32_HEAD_MAGIC, std::strlen(IPS32_HEAD_MAGIC));
uint16_t valueLength;
uint32_t bigEndianAddress;
for (const auto& patch : patches) {
bigEndianAddress = toBigEndian(patch.first); // Convert address to big-endian
ipsFile.write(reinterpret_cast<const char*>(&bigEndianAddress), sizeof(bigEndianAddress)); // Write address
valueLength = toBigEndian(static_cast<uint16_t>(patch.second.size())); // Convert length to big-endian
ipsFile.write(reinterpret_cast<const char*>(&valueLength), sizeof(valueLength)); // Write length of value
ipsFile.write(reinterpret_cast<const char*>(patch.second.data()), patch.second.size()); // Write value
}
ipsFile.write(IPS32_FOOT_MAGIC, std::strlen(IPS32_FOOT_MAGIC));
ipsFile.close();
std::ifstream _pchtxtFile(pchtxtPath);
if (!_pchtxtFile) {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Error: Unable to open file " + pchtxtPath);
#endif
return false; // Changed: Return false if we can't read the file for title ID
}
std::string pchtxt((std::istreambuf_iterator<char>(_pchtxtFile)), std::istreambuf_iterator<char>());
const std::string tid = findTitleID(pchtxt);
if (!tid.empty()) {
const std::string tidFilePath = outputFolder + tid;
std::ofstream tidFile(tidFilePath);
tidFile.close(); // Creates an empty file
} else {
#if USING_LOGGING_DIRECTIVE
if (!disableLogging)
logMessage("Warning: Could not find Title ID in " + pchtxtPath);
#endif
}
#endif
return true;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,609 @@
/********************************************************************************
* File: string_funcs.cpp
* Author: ppkantorski
* Description:
* This source file provides implementations for the string manipulation
* functions declared in string_funcs.hpp. These utility functions support
* operations such as trimming whitespace, removing quotes, normalizing
* slashes, and performing other string cleanup tasks used throughout
* the Ultrahand Overlay project.
*
* For the latest updates and contributions, visit the project's GitHub repository.
* (GitHub Repository: https://github.com/ppkantorski/Ultrahand-Overlay)
*
* Note: Please be aware that this notice cannot be altered or removed. It is a part
* of the project's documentation and must remain intact.
*
* Licensed under both GPLv2 and CC-BY-4.0
* Copyright (c) 2024 ppkantorski
********************************************************************************/
#include "string_funcs.hpp"
namespace ult {
// Custom string conversion methods in place of std::
std::string to_string(int value) {
char buffer[12]; // Sufficient for 32-bit int
snprintf(buffer, sizeof(buffer), "%d", value);
return std::string(buffer);
}
int stoi(const std::string& str, std::size_t* pos, int base) {
char* end;
const long result = std::strtol(str.c_str(), &end, base);
if (pos) {
*pos = end - str.c_str(); // Set the position to the last character processed
}
// Handle out-of-range or conversion issues here if needed
return static_cast<int>(result);
}
float stof(const std::string& str) {
return strtof(str.c_str(), nullptr);
}
// Mimics std::getline() with a delimiter
bool StringStream::getline(std::string& output, char delimiter) {
if (position >= data.size()) {
return false;
}
const size_t nextPos = data.find(delimiter, position);
if (nextPos != std::string::npos) {
output.assign(data, position, nextPos - position); // No temporary string creation
position = nextPos + 1;
} else {
output.assign(data, position, data.size() - position); // No temporary string creation
position = data.size();
}
return true;
}
// Mimics operator >> to split by whitespace
StringStream& StringStream::operator>>(std::string& output) {
// Skip leading whitespace
while (position < data.size() && std::isspace(data[position])) {
++position;
}
if (position >= data.size()) {
output.clear();
validState = false;
return *this;
}
size_t nextPos = position;
while (nextPos < data.size() && !std::isspace(data[nextPos])) {
++nextPos;
}
output.assign(data, position, nextPos - position); // Replace substr() with assign()
position = nextPos;
validState = true;
return *this;
}
// Overload << operator for std::string
StringStream& StringStream::operator<<(const std::string& input) {
data += input;
return *this;
}
// Overload << operator for const char*
StringStream& StringStream::operator<<(const char* input) {
data += input;
return *this;
}
// Overload << operator for char
StringStream& StringStream::operator<<(char input) {
data += input;
return *this;
}
// Overload << operator for int (handles hex mode)
StringStream& StringStream::operator<<(int input) {
if (hexMode) {
char buffer[20]; // Buffer large enough for hex conversion
sprintf(buffer, "%x", input); // Convert integer to hex string
data += buffer;
} else {
data += ult::to_string(input);
}
return *this;
}
// Define the new overload for long long
StringStream& StringStream::operator<<(long long input) {
data += std::to_string(input);
return *this;
}
// Return the current buffer content
std::string StringStream::str() const {
return data;
}
/**
* @brief Trims leading and trailing whitespaces from a string.
*
* This function removes leading and trailing whitespaces, tabs, newlines, carriage returns, form feeds,
* and vertical tabs from the input string.
*
* @param str The input string to trim.
* @return The trimmed string.
*/
void trim(std::string& str) {
const size_t first = str.find_first_not_of(" \t\n\r\f\v");
if (first == std::string::npos) {
str.clear(); // Fix: clear all-whitespace strings
return;
}
const size_t last = str.find_last_not_of(" \t\n\r\f\v");
// True in-place modification - no temporary string creation
if (last + 1 < str.length()) {
str.erase(last + 1); // Remove trailing whitespace
}
if (first > 0) {
str.erase(0, first); // Remove leading whitespace
}
}
// Function to trim newline characters from the end of a string
void trimNewline(std::string& str) {
const size_t end = str.find_last_not_of("\n");
if (end == std::string::npos) {
str.clear(); // If the string consists entirely of newlines, clear it
} else {
str.erase(end + 1); // Remove all characters after the last non-newline character
}
}
/**
* @brief Removes all white spaces from a string.
*
* This function removes all white spaces, including spaces, tabs, newlines, carriage returns, form feeds,
* and vertical tabs from the input string.
*
* @param str The input string to remove white spaces from.
* @return The string with white spaces removed.
*/
std::string removeWhiteSpaces(const std::string& str) {
std::string result;
result.reserve(str.size()); // Reserve space for the result to avoid reallocations
std::remove_copy_if(str.begin(), str.end(), std::back_inserter(result), [](unsigned char c) {
return std::isspace(c);
});
return result;
}
/**
* @brief Removes quotes from a string.
*
* This function removes single and double quotes from the beginning and end of the input string.
*
* @param str The input string to remove quotes from.
* @return The string with quotes removed.
*/
void removeQuotes(std::string& str) {
if (str.size() >= 2) {
const char front = str[0];
const char back = str[str.size() - 1];
if ((front == '\'' && back == '\'') || (front == '"' && back == '"')) {
str.erase(0, 1);
str.pop_back();
}
}
}
/**
* @brief Replaces multiple consecutive slashes with a single slash in a string.
*
* This function replaces sequences of two or more consecutive slashes with a single slash in the input string.
*
* @param input The input string to process.
* @return The string with multiple slashes replaced.
*/
std::string replaceMultipleSlashes(const std::string& input) {
std::string output;
output.reserve(input.size()); // Reserve space for the output string
bool previousSlash = false;
for (char c : input) {
if (c == '/') {
if (!previousSlash) {
output.push_back(c);
}
previousSlash = true;
} else {
output.push_back(c);
previousSlash = false;
}
}
return output;
}
/**
* @brief Preprocesses a path string by replacing multiple slashes and adding "sdmc:" prefix.
*
* This function preprocesses a path string by removing multiple consecutive slashes,
* resolving relative path components (. and ..), adding the "sdmc:" prefix if not present,
* and modifying the input string in place.
*
* @param path The input path string to preprocess, passed by reference.
* @param packagePath The base package path to resolve relative paths against.
*/
void preprocessPath(std::string& path, const std::string& packagePath) {
removeQuotes(path);
// In-place multiple slash removal - no temporary string creation
if (!path.empty()) {
size_t writePos = 0;
bool previousSlash = false;
for (size_t i = 0; i < path.length(); ++i) {
if (path[i] == '/') {
if (!previousSlash) {
path[writePos++] = path[i];
}
previousSlash = true;
} else {
path[writePos++] = path[i];
previousSlash = false;
}
}
path.resize(writePos);
}
// First handle "./" replacement if present
if (!packagePath.empty() && path.length() >= 2 && path[0] == '.' && path[1] == '/') {
// Handle "./" - replace with packagePath
path.replace(0, 2, packagePath);
}
size_t dotDotPos, lastSlash;
std::string beforeDotDot;
// Then handle any "../" sequences that may exist anywhere in the path
while (!path.empty()) {
dotDotPos = path.find("../");
if (dotDotPos == std::string::npos) {
break; // No more "../" sequences found
}
// Found "../" sequence at dotDotPos
beforeDotDot = path.substr(0, dotDotPos);
// Remove trailing slash from the part before "../"
if (!beforeDotDot.empty() && beforeDotDot.back() == '/') {
beforeDotDot.pop_back();
}
// Go up one level
lastSlash = beforeDotDot.find_last_of('/');
if (lastSlash != std::string::npos) {
beforeDotDot = beforeDotDot.substr(0, lastSlash + 1);
} else {
// No slash found, we're at root level
beforeDotDot = "/";
}
// Replace the path up to and including this "../" with the resolved path
path = beforeDotDot + path.substr(dotDotPos + 3);
}
// Direct character comparison instead of substr() for sdmc: prefix
if (path.length() < 5 ||
path[0] != 's' || path[1] != 'd' || path[2] != 'm' || path[3] != 'c' || path[4] != ':') {
path.insert(0, "sdmc:");
}
}
/**
* @brief Preprocesses a URL string by adding "https://" prefix.
*
* This function preprocesses a URL string by adding the "https://" prefix if not already present.
*
* @param path The input URL string to preprocess, passed by reference and modified in-place.
*/
void preprocessUrl(std::string& path) {
removeQuotes(path);
if ((path.compare(0, 7, "http://") == 0) || (path.compare(0, 8, "https://") == 0)) {
return; // No need to modify the string if it already has a prefix
} else {
path = "https://" + path; // Prepend "https://"
}
}
/**
* @brief Drops the file extension from a filename.
*
* This function removes the file extension (characters after the last dot) from the input filename string.
*
* @param filename The input filename from which to drop the extension, passed by reference and modified in-place.
*/
void dropExtension(std::string& filename) {
const size_t lastDotPos = filename.find_last_of('.'); // Single char instead of string
if (lastDotPos != std::string::npos) {
filename.resize(lastDotPos);
}
}
/**
* @brief Checks if a string starts with a given prefix.
*
* This function checks if the input string starts with the specified prefix.
*
* @param str The input string to check.
* @param prefix The prefix to check for.
* @return True if the string starts with the prefix, false otherwise.
*/
bool startsWith(const std::string& str, const std::string& prefix) {
return str.compare(0, prefix.length(), prefix) == 0;
}
// Helper function to check if a string is a valid integer
bool isValidNumber(const std::string& str) {
if (str.empty() || ((str[0] != '-') && !std::isdigit(str[0])) || (str[0] == '-' && str.size() == 1)) {
return false;
}
for (size_t i = 1; i < str.size(); ++i) {
if (!std::isdigit(str[i])) {
return false;
}
}
return true;
}
std::string returnOrNull(const std::string& value) {
return value.empty() ? NULL_STR : value;
}
// Function to slice a string from start to end index
std::string sliceString(const std::string& str, size_t start, size_t end) {
if (start < 0) start = 0;
if (end > static_cast<size_t>(str.length())) end = str.length();
if (start > end) start = end;
return str.substr(start, end - start);
}
//std::string addQuotesIfNeeded(const std::string& str) {
// if (str.find(' ') != std::string::npos) {
// return "\"" + str + "\"";
// }
// return str;
//}
/**
* @brief Converts a string to lowercase.
*
* This function takes a string as input and returns a lowercase version of that string.
*
* @param str The input string to convert to lowercase.
* @return The lowercase version of the input string.
*/
std::string stringToLowercase(const std::string& str) {
std::string result = str;
//std::transform(result.begin(), result.end(), result.begin(),
// [](unsigned char c) { return std::tolower(c); });
//return result;
for (char& c : result) {
if (c >= 'A' && c <= 'Z') {
c += 32;
}
}
return result;
}
/**
* @brief Converts a string to uppercase.
*
* This function takes a string as input and returns an uppercase version of that string.
*
* @param str The input string to convert to uppercase.
* @return The uppercase version of the input string.
*/
std::string stringToUppercase(const std::string& str) {
std::string result = str;
for (char& c : result) {
if (c >= 'a' && c <= 'z') {
c -= 32;
}
}
return result;
}
/**
* @brief Formats a priority string to a desired width.
*
* This function takes a priority string and formats it to a specified desired width by padding with '0's if it's shorter
* or truncating with '9's if it's longer.
*
* @param priority The input priority string to format.
* @param desiredWidth The desired width of the formatted string (default is 4).
* @return A formatted priority string.
*/
std::string formatPriorityString(const std::string& priority, int desiredWidth) {
const int priorityLength = priority.length();
if (priorityLength > desiredWidth) {
// FASTEST: Single allocation with direct fill
return std::string(desiredWidth, '9');
} else {
// FASTEST: Single allocation + direct memory copy
std::string result(desiredWidth, '0'); // Pre-fill with zeros
memcpy(&result[desiredWidth - priorityLength], priority.data(), priorityLength);
return result;
}
}
/**
* @brief Removes the part of the string after the first occurrence of '?' character.
*
* This function takes a string and removes the portion of the string that appears after
* the first '?' character, if found. If no '?' character is present, the string remains unchanged.
*
* @param input The input string from which to remove the tag, passed by reference and modified in-place.
*/
void removeTag(std::string &input) {
const size_t pos = input.find('?');
if (pos != std::string::npos) {
input.resize(pos); // Modify the string in-place to remove everything after the '?'
}
}
std::string getFirstLongEntry(const std::string& input, size_t minLength) {
StringStream iss(input); // Use custom StringStream
std::string word;
// Split the input string based on spaces and get the first word
if (iss >> word) {
// Check if the first word's length is greater than the specified length
if (word.length() > minLength) {
return word;
}
}
// Return an empty string if the first word is not longer than minLength
return input;
}
// This will take a string like "v1.3.5-abasdfasdfa" and output "1.3.5". string could also look like "test-1.3.5-1" or "v1.3.5" and we will only want "1.3.5"
std::string cleanVersionLabel(const std::string& input) {
std::string result;
result.reserve(input.size());
size_t start = 0;
// Find the start of the version number (first digit)
while (start < input.size() && !std::isdigit(input[start])) {
start++;
}
if (start == input.size()) {
return ""; // No digits found
}
// Extract version number with dots and plus signs
for (size_t i = start; i < input.size(); ++i) {
const char c = input[i];
if (std::isdigit(c) || c == '.' || c == '+') {
result += c;
} else {
break; // Stop at first character that's not digit, dot, or plus
}
}
return result;
}
std::string extractTitle(const std::string& input) {
const size_t spacePos = input.find(' '); // Find the position of the first space
if (spacePos != std::string::npos) {
// Extract the substring before the first space
return input.substr(0, spacePos);
} else {
// If no space is found, return the original string
return input;
}
}
std::vector<std::string> splitString(const std::string& str, const std::string& delimiter) {
std::vector<std::string> tokens;
// OPTIMIZATION: Pre-allocate space to avoid reallocations
tokens.reserve(str.length() / (delimiter.length() + 1) + 1);
size_t start = 0;
size_t end = str.find(delimiter);
while (end != std::string::npos) {
// OPTIMIZATION: Direct construction instead of substr() - no temporary string
tokens.emplace_back(str, start, end - start);
start = end + delimiter.length();
end = str.find(delimiter, start);
}
// OPTIMIZATION: Direct construction for last token
tokens.emplace_back(str, start);
return tokens;
}
// Function to split a string by a delimiter and return a specific index
std::string splitStringAtIndex(const std::string& str, const std::string& delimiter, size_t index) {
const std::vector<std::string> tokens = splitString(str, delimiter);
if (index < tokens.size()) {
return tokens[index];
} else {
return ""; // Return empty string if index is out of bounds
}
}
std::string customAlign(int number) {
const std::string numStr = ult::to_string(number);
const int paddingSpaces = (4 - numStr.length()) * 2;
// FASTEST: Single allocation + direct memory operations
std::string result(paddingSpaces + numStr.length(), ' ');
memcpy(&result[paddingSpaces], numStr.data(), numStr.length());
return result;
}
//#if IS_LAUNCHER_DIRECTIVE
//std::string inputExists(const std::string& input) {
// std::string e;
// for (char c : input) {
// e += (c + 5);
// }
// return e;
//}
//#endif
}

File diff suppressed because it is too large Load Diff